From 7784e48e235f514f9f1df56915e52475f23ace8e Mon Sep 17 00:00:00 2001 From: Vinoth Veeraraghavan Date: Thu, 1 Dec 2022 18:18:05 +0800 Subject: [PATCH 1/2] MOT new features: 1. JIT support for stored procedures 2. MVCC & Cross-Txn support 3. Alter Table support (Add/Drop/Rename Column) 4. Low RTO 5. More stabilizations --- src/common/backend/catalog/builtin_funcs.ini | 8 + src/common/backend/catalog/pg_proc.cpp | 37 +- src/common/backend/parser/analyze.cpp | 113 +- src/common/backend/parser/parse_relation.cpp | 8 + src/common/backend/utils/adt/pgstatfuncs.cpp | 186 + src/common/backend/utils/cache/plancache.cpp | 163 +- src/common/backend/utils/errcodes.txt | 3 +- src/common/pl/plpgsql/src/pl_exec.cpp | 44 +- src/common/pl/plpgsql/src/pl_handler.cpp | 31 +- src/gausskernel/CMakeLists.txt | 1 + .../optimizer/commands/analyze.cpp | 5 +- .../optimizer/commands/functioncmds.cpp | 19 + .../optimizer/commands/prepare.cpp | 103 + .../optimizer/commands/tablecmds.cpp | 114 +- src/gausskernel/process/postmaster/pgstat.cpp | 41 + .../process/postmaster/postmaster.cpp | 12 +- .../process/postmaster/startup.cpp | 14 - src/gausskernel/process/tcop/postgres.cpp | 113 +- src/gausskernel/process/tcop/pquery.cpp | 125 +- .../process/threadpool/knl_instance.cpp | 2 +- .../process/threadpool/knl_session.cpp | 12 +- src/gausskernel/runtime/executor/execMain.cpp | 7 +- .../runtime/executor/lightProxy.cpp | 18 +- src/gausskernel/runtime/executor/spi.cpp | 58 +- src/gausskernel/runtime/opfusion/opfusion.cpp | 43 +- .../runtime/opfusion/opfusion_mot.cpp | 106 +- .../runtime/opfusion/opfusion_util.cpp | 8 + .../storage/access/transam/xact.cpp | 54 +- .../storage/access/transam/xlog.cpp | 8 +- src/gausskernel/storage/ipc/shmem.cpp | 9 + src/gausskernel/storage/mot/CMakeLists.txt | 1 + .../storage/mot/core/Makefile.local | 11 +- .../occ_transaction_manager.cpp | 717 ++- .../occ_transaction_manager.h | 93 +- .../core/concurrency_control/row_header.cpp | 164 - .../mot/core/concurrency_control/row_header.h | 246 - .../core/infra/config/cmdline_config_loader.h | 13 +- .../core/infra/config/config_file_loader.cpp | 10 +- .../core/infra/config/config_file_loader.h | 2 +- .../core/infra/config/config_file_parser.cpp | 69 +- .../mot/core/infra/config/config_item.cpp | 2 +- .../mot/core/infra/config/config_item.h | 2 +- .../core/infra/config/config_item_class.cpp | 2 +- .../mot/core/infra/config/config_item_class.h | 2 +- .../mot/core/infra/config/config_loader.cpp | 2 +- .../mot/core/infra/config/config_loader.h | 19 +- .../mot/core/infra/config/config_manager.cpp | 50 +- .../mot/core/infra/config/config_section.cpp | 7 +- .../core/infra/config/config_value_type.cpp | 2 +- .../mot/core/infra/config/config_value_type.h | 2 +- .../mot/core/infra/config/ext_config_loader.h | 21 +- .../core/infra/config/file_line_reader.cpp | 14 +- .../mot/core/infra/config/file_line_reader.h | 12 + .../infra/config/iconfig_change_listener.h | 18 +- .../core/infra/config/layered_config_tree.cpp | 14 +- .../core/infra/config/layered_config_tree.h | 9 +- .../infra/config/props_config_file_loader.cpp | 20 +- .../core/infra/config/typed_config_value.h | 13 +- .../mot/core/infra/containers/bitmapset.cpp | 26 +- .../mot/core/infra/containers/bitmapset.h | 19 +- .../core/infra/containers/concurrent_map.h | 32 +- .../mot/core/infra/containers/mot_list.h | 11 +- .../mot/core/infra/containers/mot_map.h | 9 +- .../mot/core/infra/containers/mot_set.h | 9 +- .../mot/core/infra/containers/mot_string.cpp | 6 +- .../mot/core/infra/containers/mot_string.h | 39 +- .../mot/core/infra/containers/mot_vector.h | 22 +- .../mot/core/infra/containers/spsc_queue.h | 197 + .../storage/mot/core/infra/infra.cpp | 2 +- .../storage/mot/core/infra/infra.h | 6 +- .../core/infra/stats/global_statistics.cpp | 4 +- .../infra/stats/level_statistic_variable.cpp | 4 +- .../infra/stats/memory_statistic_variable.cpp | 6 +- .../stats/numeric_statistic_variable.cpp | 5 +- .../infra/stats/numeric_statistic_variable.h | 2 +- .../infra/stats/rate_statistic_variable.cpp | 2 +- .../mot/core/infra/stats/statistic_variable.h | 2 +- .../core/infra/stats/statistics_manager.cpp | 64 +- .../mot/core/infra/stats/statistics_manager.h | 4 +- .../core/infra/stats/statistics_provider.cpp | 26 +- .../core/infra/stats/statistics_provider.h | 29 +- .../core/infra/stats/thread_statistics.cpp | 10 +- .../mot/core/infra/stats/thread_statistics.h | 5 +- .../core/infra/synchronization/affinity.cpp | 67 +- .../mot/core/infra/synchronization/affinity.h | 19 +- .../mot/core/infra/synchronization/cycles.cpp | 2 +- .../mot/core/infra/synchronization/cycles.h | 2 +- .../infra/synchronization/mot_atomic_ops.h | 13 +- .../core/infra/synchronization/rwspinlock.h | 2 +- .../core/infra/synchronization/spin_lock.h | 4 +- .../core/infra/synchronization/thread_utils.h | 136 + .../garbage_collector/mm_gc_manager.cpp | 467 +- .../memory/garbage_collector/mm_gc_manager.h | 636 +- .../memory/garbage_collector/mm_gc_queue.cpp | 438 ++ .../memory/garbage_collector/mm_gc_queue.h | 438 ++ .../mot/core/memory/memory_statistics.cpp | 24 +- .../mot/core/memory/memory_statistics.h | 50 +- .../storage/mot/core/memory/mm_api.cpp | 14 +- .../storage/mot/core/memory/mm_api.h | 90 + .../mot/core/memory/mm_buffer_allocator.cpp | 68 +- .../mot/core/memory/mm_buffer_allocator.h | 17 + .../storage/mot/core/memory/mm_buffer_api.cpp | 48 +- .../storage/mot/core/memory/mm_buffer_api.h | 70 +- .../mot/core/memory/mm_buffer_chunk.cpp | 24 +- .../storage/mot/core/memory/mm_buffer_chunk.h | 20 +- .../mot/core/memory/mm_buffer_heap.cpp | 138 +- .../storage/mot/core/memory/mm_buffer_heap.h | 31 +- .../mot/core/memory/mm_buffer_list.cpp | 4 +- .../storage/mot/core/memory/mm_cfg.cpp | 67 +- .../storage/mot/core/memory/mm_chunk_type.h | 2 +- .../storage/mot/core/memory/mm_def.cpp | 25 +- .../storage/mot/core/memory/mm_def.h | 35 +- .../storage/mot/core/memory/mm_global_api.cpp | 93 +- .../storage/mot/core/memory/mm_global_api.h | 2 +- .../core/memory/mm_huge_object_allocator.cpp | 63 +- .../core/memory/mm_huge_object_allocator.h | 2 +- .../storage/mot/core/memory/mm_lock.h | 8 +- .../storage/mot/core/memory/mm_numa.cpp | 36 +- .../storage/mot/core/memory/mm_numa.h | 4 +- .../mot/core/memory/mm_raw_chunk_dir.cpp | 6 +- .../mot/core/memory/mm_raw_chunk_pool.cpp | 175 +- .../mot/core/memory/mm_raw_chunk_pool.h | 29 + .../mot/core/memory/mm_raw_chunk_store.cpp | 74 +- .../mot/core/memory/mm_raw_chunk_store.h | 14 + .../mot/core/memory/mm_session_allocator.cpp | 79 +- .../mot/core/memory/mm_session_allocator.h | 4 +- .../mot/core/memory/mm_session_api.cpp | 58 +- .../storage/mot/core/memory/mm_session_api.h | 12 +- .../memory/mm_session_large_buffer_list.cpp | 30 +- .../memory/mm_session_large_buffer_list.h | 55 +- .../memory/mm_session_large_buffer_pool.cpp | 57 +- .../memory/mm_session_large_buffer_store.cpp | 12 +- .../memory/mm_session_large_buffer_store.h | 2 +- .../mot/core/memory/mm_virtual_huge_chunk.cpp | 6 +- .../mot/core/memory/mm_virtual_huge_chunk.h | 4 +- .../storage/mot/core/memory/object_pool.cpp | 32 +- .../storage/mot/core/memory/object_pool.h | 234 +- .../mot/core/memory/object_pool_compact.cpp | 6 +- .../mot/core/memory/object_pool_compact.h | 11 +- .../mot/core/memory/object_pool_impl.cpp | 213 +- .../mot/core/memory/object_pool_impl.h | 384 +- .../mot/core/memory/spsc_allocator.cpp | 60 + .../storage/mot/core/memory/spsc_allocator.h | 179 + .../storage/mot/core/memory/sys_numa_api.cpp | 136 +- .../storage/mot/core/memory/sys_numa_api.h | 2 +- src/gausskernel/storage/mot/core/mot.conf | 65 +- .../storage/mot/core/storage/CMakeLists.txt | 2 + .../mot/core/storage/catalog_column_types.h | 2 +- .../storage/mot/core/storage/column.cpp | 248 +- .../storage/mot/core/storage/column.h | 74 +- .../storage/mot/core/storage/index/index.cpp | 307 +- .../storage/mot/core/storage/index/index.h | 349 +- .../mot/core/storage/index/index_base.h | 2 +- .../mot/core/storage/index/index_defs.cpp | 2 - .../mot/core/storage/index/index_defs.h | 7 +- .../mot/core/storage/index/index_factory.cpp | 2 - .../mot/core/storage/index/index_iterator.h | 4 +- .../storage/index/masstree/mot_masstree.hpp | 5 - .../index/masstree/mot_masstree_get.hpp | 1 - .../index/masstree/mot_masstree_insert.hpp | 4 - .../index/masstree/mot_masstree_iterator.hpp | 23 +- .../index/masstree/mot_masstree_kvthread.cpp | 37 +- .../index/masstree/mot_masstree_remove.hpp | 12 +- .../index/masstree/mot_masstree_struct.hpp | 13 +- .../mot/core/storage/index/masstree_index.cpp | 68 +- .../mot/core/storage/index/masstree_index.h | 259 +- .../storage/index/surrogate_key_manager.cpp | 2 +- .../storage/mot/core/storage/key.h | 20 +- .../storage/mot/core/storage/row.cpp | 138 +- .../storage/mot/core/storage/row.h | 361 +- .../mot/core/storage/sentinel/CMakeLists.txt | 12 + .../core/storage/sentinel/gc_shared_info.h | 121 + .../storage/sentinel/primary_sentinel.cpp | 227 + .../core/storage/sentinel/primary_sentinel.h | 143 + .../storage/sentinel/primary_sentinel_node.h | 122 + .../storage/sentinel/secondary_sentinel.cpp | 50 + .../storage/sentinel/secondary_sentinel.h | 130 + .../sentinel/secondary_sentinel_unique.cpp | 148 + .../sentinel/secondary_sentinel_unique.h | 104 + .../core/storage/{ => sentinel}/sentinel.cpp | 34 +- .../core/storage/{ => sentinel}/sentinel.h | 294 +- .../storage/mot/core/storage/table.cpp | 671 +- .../storage/mot/core/storage/table.h | 464 +- .../storage/mot/core/storage/txn_table.cpp | 860 +++ .../storage/mot/core/storage/txn_table.h | 223 + .../system/checkpoint/checkpoint_ctrlfile.cpp | 28 +- .../system/checkpoint/checkpoint_ctrlfile.h | 22 +- .../system/checkpoint/checkpoint_manager.cpp | 417 +- .../system/checkpoint/checkpoint_manager.h | 82 +- .../system/checkpoint/checkpoint_utils.cpp | 26 +- .../core/system/checkpoint/checkpoint_utils.h | 141 +- .../system/checkpoint/checkpoint_worker.cpp | 380 +- .../system/checkpoint/checkpoint_worker.h | 93 +- .../mot/core/system/common/connection_id.cpp | 22 +- .../mot/core/system/common/connection_id.h | 2 +- ...it_sequence_number.cpp => csn_manager.cpp} | 33 +- ...commit_sequence_number.h => csn_manager.h} | 42 +- .../mot/core/system/common/gc_context.h | 4 +- .../mot/core/system/common/icsn_manager.h | 61 + .../core/system/common/session_context.cpp | 121 +- .../mot/core/system/common/session_context.h | 17 +- .../core/system/common/session_manager.cpp | 64 +- .../mot/core/system/common/session_manager.h | 9 +- .../mot/core/system/common/table_manager.cpp | 8 +- .../mot/core/system/common/table_manager.h | 35 +- .../mot/core/system/common/thread_id.cpp | 46 +- .../mot/core/system/common/thread_id.h | 2 +- .../storage/mot/core/system/global.cpp | 17 +- .../storage/mot/core/system/global.h | 62 +- .../mot/core/system/mot_configuration.cpp | 312 +- .../mot/core/system/mot_configuration.h | 114 +- .../storage/mot/core/system/mot_engine.cpp | 73 +- .../storage/mot/core/system/mot_engine.h | 133 +- .../storage/mot/core/system/mot_error.cpp | 31 +- .../storage/mot/core/system/mot_error.h | 8 +- .../storage/mot/core/system/mot_error_codes.h | 6 + .../system/recovery/base_recovery_manager.cpp | 206 + .../system/recovery/base_recovery_manager.h | 245 + .../system/recovery/checkpoint_recovery.cpp | 510 +- .../system/recovery/checkpoint_recovery.h | 84 +- .../recovery/inprocess_transactions.cpp | 82 - .../system/recovery/inprocess_transactions.h | 127 - .../core/system/recovery/irecovery_manager.h | 106 +- .../system/recovery/irecovery_ops_context.h | 43 + .../mot/core/system/recovery/log_segment.h | 33 +- .../system/recovery/mtls_recovery_manager.cpp | 1013 +++ .../system/recovery/mtls_recovery_manager.h | 335 + .../recovery/mtls_transaction_committer.cpp | 112 + .../recovery/mtls_transaction_committer.h | 162 + .../recovery/mtls_transaction_processor.cpp | 128 + .../recovery/mtls_transaction_processor.h | 192 + .../core/system/recovery/recovery_manager.cpp | 410 -- .../core/system/recovery/recovery_manager.h | 345 - .../recovery/recovery_manager_factory.h | 13 +- .../core/system/recovery/recovery_mode.cpp | 59 + .../mot/core/system/recovery/recovery_mode.h | 86 + .../mot/core/system/recovery/recovery_ops.cpp | 960 +-- .../mot/core/system/recovery/recovery_ops.h | 188 +- .../mot/core/system/recovery/recovery_stats.h | 184 + .../redo_log_transaction_iterator.cpp | 16 +- .../recovery/redo_log_transaction_iterator.h | 17 +- .../recovery/redo_log_transaction_player.cpp | 162 + .../recovery/redo_log_transaction_player.h | 173 + .../redo_log_transaction_segments.cpp | 85 - .../recovery/redo_log_transaction_segments.h | 81 - .../core/system/recovery/surrogate_state.cpp | 43 +- .../core/system/recovery/surrogate_state.h | 22 +- .../statistics/db_session_statistics.cpp | 12 +- .../system/statistics/db_session_statistics.h | 6 +- .../system/statistics/network_statistics.cpp | 12 +- .../system/statistics/network_statistics.h | 4 +- .../system/statistics/process_statistics.cpp | 28 +- .../system/statistics/process_statistics.h | 10 +- .../system/statistics/system_statistics.cpp | 31 +- .../system/statistics/system_statistics.h | 10 +- .../mot/core/system/transaction/access.cpp | 119 + .../mot/core/system/transaction/access.h | 154 +- .../core/system/transaction/access_params.h | 72 +- .../core/system/transaction/sub_txn_mgr.cpp | 101 + .../mot/core/system/transaction/sub_txn_mgr.h | 158 + .../mot/core/system/transaction/txn.cpp | 1703 +++-- .../storage/mot/core/system/transaction/txn.h | 367 +- .../core/system/transaction/txn_access.cpp | 1587 ++++- .../mot/core/system/transaction/txn_access.h | 255 +- .../system/transaction/txn_ddl_access.cpp | 107 +- .../core/system/transaction/txn_ddl_access.h | 58 +- .../system/transaction/txn_insert_action.h | 68 +- .../system/transaction/txn_local_allocators.h | 81 +- .../transaction_logger/base_txn_logger.cpp | 433 ++ .../transaction_logger/base_txn_logger.h | 185 + .../commit_group.cpp | 16 +- .../group_synchronous_redo_log/commit_group.h | 6 +- ...er.cpp => group_sync_redo_log_handler.cpp} | 10 +- ...andler.h => group_sync_redo_log_handler.h} | 12 +- ...segmented_group_sync_redo_log_handler.cpp} | 15 +- ...> segmented_group_sync_redo_log_handler.h} | 12 +- .../transaction_logger/log_statistics.cpp | 12 +- .../transaction_logger/log_statistics.h | 17 +- .../system/transaction_logger/logger_type.cpp | 2 +- .../transaction_logger/pending_txn_logger.cpp | 92 + .../transaction_logger/pending_txn_logger.h | 79 + .../system/transaction_logger/redo_log.cpp | 340 +- .../core/system/transaction_logger/redo_log.h | 114 +- .../transaction_logger/redo_log_buffer.h | 49 +- .../transaction_logger/redo_log_global.h | 84 +- .../transaction_logger/redo_log_handler.cpp | 15 +- .../transaction_logger/redo_log_handler.h | 3 +- .../redo_log_handler_type.cpp | 12 +- .../redo_log_handler_type.h | 2 +- .../transaction_logger/redo_log_writer.cpp | 277 +- .../transaction_logger/redo_log_writer.h | 95 +- ..._handler.cpp => sync_redo_log_handler.cpp} | 33 +- ..._log_handler.h => sync_redo_log_handler.h} | 22 +- .../storage/mot/core/utils/buffer.h | 41 +- .../storage/mot/core/utils/debug_utils.cpp | 42 +- .../storage/mot/core/utils/debug_utils.h | 6 +- .../storage/mot/core/utils/log_level.cpp | 12 +- .../storage/mot/core/utils/log_level.h | 5 +- .../storage/mot/core/utils/logger.cpp | 36 +- .../storage/mot/core/utils/mot_log.cpp | 28 +- .../storage/mot/core/utils/mot_log.h | 29 +- .../storage/mot/core/utils/serializable.h | 12 +- .../storage/mot/core/utils/string_buffer.cpp | 10 +- .../storage/mot/core/utils/string_buffer.h | 14 +- .../storage/mot/core/utils/utilities.cpp | 11 +- .../storage/mot/core/utils/utilities.h | 68 +- .../storage/mot/fdw_adapter/Makefile | 15 +- .../mot/fdw_adapter/gaussdb_config_loader.h | 48 +- .../storage/mot/fdw_adapter/mot_fdw.cpp | 1275 ++-- .../storage/mot/fdw_adapter/mot_fdw_error.cpp | 25 +- .../mot/fdw_adapter/mot_fdw_helpers.cpp | 968 +++ .../storage/mot/fdw_adapter/mot_fdw_helpers.h | 83 + .../fdw_adapter/mot_fdw_snapshot_manager.cpp | 55 + .../fdw_adapter/mot_fdw_snapshot_manager.h | 51 + .../storage/mot/fdw_adapter/mot_fdw_xlog.cpp | 7 +- .../storage/mot/fdw_adapter/mot_fdw_xlog.h | 4 +- .../storage/mot/fdw_adapter/mot_internal.cpp | 1110 ++-- .../storage/mot/fdw_adapter/mot_internal.h | 531 +- .../mot/fdw_adapter/mot_match_index.cpp | 109 +- .../storage/mot/fdw_adapter/mot_match_index.h | 41 +- src/gausskernel/storage/mot/jit_exec/Makefile | 16 +- .../storage/mot/jit_exec/jit_common.cpp | 1222 +++- .../storage/mot/jit_exec/jit_common.h | 295 +- .../storage/mot/jit_exec/jit_context.cpp | 3833 ++++++++++-- .../storage/mot/jit_exec/jit_context.h | 859 ++- .../storage/mot/jit_exec/jit_context_pool.cpp | 436 +- .../storage/mot/jit_exec/jit_context_pool.h | 54 +- .../storage/mot/jit_exec/jit_exec.cpp | 2263 ++++++- .../storage/mot/jit_exec/jit_explain.cpp | 349 +- .../storage/mot/jit_exec/jit_helpers.cpp | 3884 ++++++++++-- .../storage/mot/jit_exec/jit_helpers.h | 327 +- .../storage/mot/jit_exec/jit_llvm.cpp | 24 +- .../storage/mot/jit_exec/jit_llvm.h | 18 +- .../storage/mot/jit_exec/jit_llvm_blocks.cpp | 2284 ++++--- .../storage/mot/jit_exec/jit_llvm_blocks.h | 66 +- .../storage/mot/jit_exec/jit_llvm_funcs.h | 1077 ++-- .../storage/mot/jit_exec/jit_llvm_query.h | 55 +- .../mot/jit_exec/jit_llvm_query_codegen.cpp | 2172 ++++--- .../mot/jit_exec/jit_llvm_query_codegen.h | 36 +- .../storage/mot/jit_exec/jit_llvm_sp.cpp | 5560 +++++++++++++++++ .../storage/mot/jit_exec/jit_llvm_sp.h | 185 + .../mot/jit_exec/jit_llvm_sp_codegen.h | 53 + .../storage/mot/jit_exec/jit_llvm_util.cpp | 1103 +++- .../storage/mot/jit_exec/jit_llvm_util.h | 718 ++- .../storage/mot/jit_exec/jit_pgproc.h | 483 -- .../storage/mot/jit_exec/jit_plan.cpp | 1215 ++-- .../storage/mot/jit_exec/jit_plan.h | 205 +- .../storage/mot/jit_exec/jit_plan_expr.cpp | 968 ++- .../storage/mot/jit_exec/jit_plan_expr.h | 244 +- .../storage/mot/jit_exec/jit_plan_sp.cpp | 1313 ++++ .../storage/mot/jit_exec/jit_plan_sp.h | 45 + .../storage/mot/jit_exec/jit_plan_sp_expr.cpp | 1278 ++++ .../storage/mot/jit_exec/jit_plan_sp_expr.h | 188 + .../storage/mot/jit_exec/jit_profiler.cpp | 562 ++ .../storage/mot/jit_exec/jit_profiler.h | 385 ++ .../storage/mot/jit_exec/jit_source.cpp | 2435 +++++++- .../storage/mot/jit_exec/jit_source.h | 325 +- .../storage/mot/jit_exec/jit_source_map.cpp | 2076 +++++- .../storage/mot/jit_exec/jit_source_map.h | 102 +- .../storage/mot/jit_exec/jit_source_pool.cpp | 141 +- .../storage/mot/jit_exec/jit_source_pool.h | 19 +- .../storage/mot/jit_exec/jit_statistics.cpp | 20 +- .../storage/mot/jit_exec/jit_statistics.h | 39 +- .../storage/mot/jit_exec/jit_tvm.cpp | 1071 ---- .../storage/mot/jit_exec/jit_tvm.h | 1395 ----- .../storage/mot/jit_exec/jit_tvm_blocks.cpp | 2240 ------- .../storage/mot/jit_exec/jit_tvm_blocks.h | 92 - .../storage/mot/jit_exec/jit_tvm_funcs.h | 3195 ---------- .../storage/mot/jit_exec/jit_tvm_query.h | 73 - .../mot/jit_exec/jit_tvm_query_codegen.cpp | 1784 ------ .../mot/jit_exec/jit_tvm_query_codegen.h | 69 - .../storage/mot/jit_exec/jit_tvm_util.cpp | 288 - .../storage/mot/jit_exec/jit_tvm_util.h | 397 -- .../storage/mot/jit_exec/jit_util.h | 199 +- src/include/access/xact.h | 4 +- src/include/commands/prepare.h | 4 + src/include/executor/exec/execdesc.h | 6 +- src/include/knl/knl_instance.h | 2 +- src/include/knl/knl_session.h | 41 +- src/include/nodes/execnodes.h | 4 +- src/include/nodes/nodes.h | 4 + src/include/nodes/parsenodes.h | 20 + src/include/opfusion/opfusion_mot.h | 22 +- src/include/parser/analyze.h | 7 - src/include/pgstat.h | 33 + src/include/storage/mot/jit_def.h | 142 +- src/include/storage/mot/jit_exec.h | 157 +- src/include/storage/mot/mot_fdw.h | 11 + src/include/utils/builtins.h | 2 + src/include/utils/knl_globalsystupcache.h | 1 - src/include/utils/plancache.h | 7 +- src/include/utils/plpgsql.h | 6 + src/test/ha/GNUmakefile | 1 + src/test/ha/deploy_multi_cascade_mot.sh | 70 + src/test/ha/ha_schedule_multi_cascade_mot | 4 + src/test/ha/pgxc_multi_cascade_mot.py | 413 ++ src/test/ha/run_ha_multi_cascade_mot.sh | 58 + src/test/ha/testcase/cascade/failover_mot.sh | 86 + .../cascade/failover_with_data_mot.sh | 127 + .../cascade/inc_build_failover_mot.sh | 90 + .../ha/testcase/cascade/switchover_mot.sh | 147 + .../mot/single_alter_foreign_table.out | 3 +- .../regress/expected/mot/single_float8.out | 2 +- .../mot/single_join_cross_engine_check.out | 1882 +++++- .../expected/mot/single_merge_privilege.out | 12 +- .../expected/mot/single_new_indexes.out | 234 + .../expected/mot/single_new_indexes2.out | 224 + .../expected/mot/single_new_indexes3.out | 949 +++ .../expected/mot/single_sp_queries_rtd.out | 271 + .../expected/mot/single_sp_queries_rtd2.out | 160 + .../expected/mot/single_sp_queries_rtd3.out | 145 + .../expected/mot/single_sp_queries_rtd5.out | 170 + .../expected/mot/single_sp_queries_rtd6.out | 128 + .../single_supported_unsupported_types.out | 3 + .../regress/expected/mot/single_update.out | 2 +- .../single_update_secondary_index_column.out | 559 ++ .../expected/single_node_opr_sanity.out | 2 + src/test/regress/parallel_schedule20 | 9 + src/test/regress/single_check.sh | 3 +- .../regress/sql/mot/single_new_indexes.sql | 172 + .../regress/sql/mot/single_new_indexes2.sql | 174 + .../regress/sql/mot/single_new_indexes3.sql | 424 ++ .../regress/sql/mot/single_sp_queries_rtd.sql | 268 + .../sql/mot/single_sp_queries_rtd2.sql | 147 + .../sql/mot/single_sp_queries_rtd3.sql | 155 + .../sql/mot/single_sp_queries_rtd5.sql | 92 + .../sql/mot/single_sp_queries_rtd6.sql | 106 + .../single_supported_unsupported_types.sql | 1 + .../single_update_secondary_index_column.sql | 294 + 429 files changed, 64989 insertions(+), 27141 deletions(-) delete mode 100644 src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp delete mode 100644 src/gausskernel/storage/mot/core/concurrency_control/row_header.h create mode 100644 src/gausskernel/storage/mot/core/infra/containers/spsc_queue.h create mode 100644 src/gausskernel/storage/mot/core/infra/synchronization/thread_utils.h create mode 100644 src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.cpp create mode 100644 src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.h create mode 100644 src/gausskernel/storage/mot/core/memory/spsc_allocator.cpp create mode 100644 src/gausskernel/storage/mot/core/memory/spsc_allocator.h create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/CMakeLists.txt create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/gc_shared_info.h create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.cpp create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.h create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel_node.h create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.cpp create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.h create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.cpp create mode 100644 src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.h rename src/gausskernel/storage/mot/core/storage/{ => sentinel}/sentinel.cpp (70%) rename src/gausskernel/storage/mot/core/storage/{ => sentinel}/sentinel.h (51%) create mode 100644 src/gausskernel/storage/mot/core/storage/txn_table.cpp create mode 100644 src/gausskernel/storage/mot/core/storage/txn_table.h rename src/gausskernel/storage/mot/core/system/common/{commit_sequence_number.cpp => csn_manager.cpp} (63%) rename src/gausskernel/storage/mot/core/system/common/{commit_sequence_number.h => csn_manager.h} (63%) create mode 100644 src/gausskernel/storage/mot/core/system/common/icsn_manager.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.h delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.cpp delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/irecovery_ops_context.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.h delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/recovery_manager.cpp delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/recovery_manager.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/recovery_mode.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/recovery_mode.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/recovery_stats.h create mode 100644 src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.cpp create mode 100644 src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.h delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.cpp delete mode 100644 src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.h create mode 100644 src/gausskernel/storage/mot/core/system/transaction/access.cpp create mode 100644 src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.cpp create mode 100644 src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.h create mode 100644 src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.cpp create mode 100644 src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.h rename src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/{group_synchronous_redo_log_handler.cpp => group_sync_redo_log_handler.cpp} (95%) rename src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/{group_synchronous_redo_log_handler.h => group_sync_redo_log_handler.h} (89%) rename src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/{segmented_group_synchronous_redo_log_handler.cpp => segmented_group_sync_redo_log_handler.cpp} (87%) rename src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/{segmented_group_synchronous_redo_log_handler.h => segmented_group_sync_redo_log_handler.h} (86%) create mode 100644 src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.cpp create mode 100644 src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.h rename src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/{synchronous_redo_log_handler.cpp => sync_redo_log_handler.cpp} (63%) rename src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/{synchronous_redo_log_handler.h => sync_redo_log_handler.h} (73%) create mode 100644 src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp create mode 100644 src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.h create mode 100644 src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.cpp create mode 100644 src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.h create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.cpp create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.h create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_llvm_sp_codegen.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_pgproc.h create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_plan_sp.cpp create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_plan_sp.h create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.cpp create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.h create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_profiler.cpp create mode 100644 src/gausskernel/storage/mot/jit_exec/jit_profiler.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm.cpp delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.cpp delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_funcs.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_query.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.cpp delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.h delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_util.cpp delete mode 100644 src/gausskernel/storage/mot/jit_exec/jit_tvm_util.h create mode 100644 src/test/ha/deploy_multi_cascade_mot.sh create mode 100644 src/test/ha/ha_schedule_multi_cascade_mot create mode 100644 src/test/ha/pgxc_multi_cascade_mot.py create mode 100644 src/test/ha/run_ha_multi_cascade_mot.sh create mode 100644 src/test/ha/testcase/cascade/failover_mot.sh create mode 100644 src/test/ha/testcase/cascade/failover_with_data_mot.sh create mode 100644 src/test/ha/testcase/cascade/inc_build_failover_mot.sh create mode 100644 src/test/ha/testcase/cascade/switchover_mot.sh create mode 100644 src/test/regress/expected/mot/single_new_indexes.out create mode 100644 src/test/regress/expected/mot/single_new_indexes2.out create mode 100644 src/test/regress/expected/mot/single_new_indexes3.out create mode 100644 src/test/regress/expected/mot/single_sp_queries_rtd.out create mode 100644 src/test/regress/expected/mot/single_sp_queries_rtd2.out create mode 100644 src/test/regress/expected/mot/single_sp_queries_rtd3.out create mode 100644 src/test/regress/expected/mot/single_sp_queries_rtd5.out create mode 100644 src/test/regress/expected/mot/single_sp_queries_rtd6.out create mode 100644 src/test/regress/expected/mot/single_update_secondary_index_column.out create mode 100644 src/test/regress/sql/mot/single_new_indexes.sql create mode 100644 src/test/regress/sql/mot/single_new_indexes2.sql create mode 100644 src/test/regress/sql/mot/single_new_indexes3.sql create mode 100644 src/test/regress/sql/mot/single_sp_queries_rtd.sql create mode 100644 src/test/regress/sql/mot/single_sp_queries_rtd2.sql create mode 100644 src/test/regress/sql/mot/single_sp_queries_rtd3.sql create mode 100644 src/test/regress/sql/mot/single_sp_queries_rtd5.sql create mode 100644 src/test/regress/sql/mot/single_sp_queries_rtd6.sql create mode 100644 src/test/regress/sql/mot/single_update_secondary_index_column.sql diff --git a/src/common/backend/catalog/builtin_funcs.ini b/src/common/backend/catalog/builtin_funcs.ini index 8e324e561..b3e340bfa 100755 --- a/src/common/backend/catalog/builtin_funcs.ini +++ b/src/common/backend/catalog/builtin_funcs.ini @@ -6988,6 +6988,14 @@ "mot_global_memory_detail", 1, AddBuiltinFunc(_0(6201), _1("mot_global_memory_detail"), _2(0), _3(false), _4(true), _5(mot_global_memory_detail), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(100), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(3, 23, 20, 20), _22(3, 'o', 'o', 'o'), _23(3, "numa_node", "reserved_size", "used_size"), _24(NULL), _25("mot_global_memory_detail"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(false), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false), _38(NULL), _39(NULL), _40(0)) ), + AddFuncGroup( + "mot_jit_detail", 1, + AddBuiltinFunc(_0(6205), _1("mot_jit_detail"), _2(0), _3(false), _4(true), _5(mot_jit_detail), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(1000), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(11, 26, 25, 25, 25, 25, 1184, 25, 20, 20, 20, 20), _22(11, 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o'), _23(11, "proc_oid", "query", "namespace", "jittable_status", "valid_status", "last_updated", "plan_type", "codegen_time", "verify_time", "finalize_time", "compile_time"), _24(NULL), _25("mot_jit_detail"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(false), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "mot_jit_profile", 1, + AddBuiltinFunc(_0(6206), _1("mot_jit_profile"), _2(0), _3(false), _4(true), _5(mot_jit_profile), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(1000), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(12, 26, 23, 23, 25, 25, 700, 20, 20, 20, 20, 20, 20), _22(12, 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o'), _23(12, "proc_oid", "id", "parent_id", "query", "namespace", "weight", "total", "self", "child_gross", "child_net", "def_vars", "init_vars"), _24(NULL), _25("mot_jit_profile"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(false), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) + ), AddFuncGroup( "mot_local_memory_detail", 1, AddBuiltinFunc(_0(6202), _1("mot_local_memory_detail"), _2(0), _3(false), _4(true), _5(mot_local_memory_detail), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(100), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(3, 23, 20, 20), _22(3, 'o', 'o', 'o'), _23(3, "numa_node", "reserved_size", "used_size"), _24(NULL), _25("mot_local_memory_detail"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(false), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false), _38(NULL), _39(NULL), _40(0)) diff --git a/src/common/backend/catalog/pg_proc.cpp b/src/common/backend/catalog/pg_proc.cpp index 51eacf56e..ef465b2ca 100644 --- a/src/common/backend/catalog/pg_proc.cpp +++ b/src/common/backend/catalog/pg_proc.cpp @@ -73,6 +73,10 @@ #include "storage/lmgr.h" #include "libpq/md5.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif + #define TEMPSEPARATOR '@' #define SEPARATOR '#' @@ -1611,6 +1615,14 @@ Oid ProcedureCreate(const char* procedureName, Oid procNamespace, Oid propackage /* send invalid message for for relation holding replaced function as trigger */ InvalidRelcacheForTriggerFunction(retval, ((Form_pg_proc)GETSTRUCT(tup))->prorettype); + +#ifdef ENABLE_MOT + if (proIsProcedure && !package && JitExec::IsMotSPCodegenEnabled()) { + /* Notify MOT that current function is about to be modified */ + JitExec::PurgeJitSourceCache( + retval, JitExec::JIT_PURGE_SCOPE_SP, JitExec::JIT_PURGE_REPLACE, procedureName); + } +#endif } myself.classId = ProcedureRelationId; @@ -2097,12 +2109,27 @@ bool function_parse_error_transpose(const char* prosrc) } } - /* We can get the original query text from the active portal (hack...) */ - Assert(ActivePortal && ActivePortal->status == PORTAL_ACTIVE); - queryText = ActivePortal->sourceText; +#ifdef ENABLE_MOT + /* + * When MOT JIT is active we need to be careful, because we might not have a portal yet. + * This can happen when we trigger compilation of a function during a PERPARE statement + * (and not through a CREATE FUNCTION command). In this case the portal does not exist + * yet, and we report the error relative to function body text. + */ + if (ActivePortal == nullptr) { + newerrposition = match_prosrc_to_query(prosrc, prosrc, origerrposition); + } else { +#endif + /* We can get the original query text from the active portal (hack...) */ + Assert(ActivePortal && ActivePortal->status == PORTAL_ACTIVE); + queryText = ActivePortal->sourceText; + + /* Try to locate the prosrc in the original text */ + newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition); +#ifdef ENABLE_MOT + } +#endif - /* Try to locate the prosrc in the original text */ - newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition); if (newerrposition > 0) { /* Successful, so fix error position to reference original query */ errposition(newerrposition); diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp index 7b8c5b4f6..38a61c099 100644 --- a/src/common/backend/parser/analyze.cpp +++ b/src/common/backend/parser/analyze.cpp @@ -90,6 +90,10 @@ #include "db4ai/create_model.h" #include "db4ai/hyperparameter_validation.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif + #ifndef ENABLE_MULTIPLE_NODES #include "optimizer/clauses.h" #endif @@ -1314,6 +1318,11 @@ void CheckTablesStorageEngine(Query* qry, StorageEngineType* type) } } + /* MOT JIT: check if this is an invoke MOT JITted SP */ + if (JitExec::IsInvokeReadyFunction(qry)) { + context.isMotTable = true; + } + /* add root query to query stack list */ context.queryNodes = lappend(context.queryNodes, qry); @@ -1328,110 +1337,6 @@ void CheckTablesStorageEngine(Query* qry, StorageEngineType* type) *type = SE_TYPE_MOT; } } - -/* - * @brief: Expression_tree_walker callback function. - * Checks the query and all sub queries to identify if there is update on indexed column. - */ -static bool MotIndexedColumnUpdateWalker(Node* node, UpdateDetectorContext* context) -{ - if (node == NULL) { - return false; - } - - if (IsA(node, RangeTblRef)) { - RangeTblRef* rtr = (RangeTblRef*)node; - Query* qry = (Query*)llast(context->queryNodes); - RangeTblEntry* rte = rt_fetch(rtr->rtindex, qry->rtable); - if (qry->commandType == CMD_UPDATE && rte->rtekind == RTE_RELATION && rte->relkind == RELKIND_FOREIGN_TABLE && - isMOTFromTblOid(rte->relid)) { - Relation rel = relation_open(rte->relid, AccessShareLock); - Bitmapset* idx_bmps = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL); - relation_close(rel, NoLock); - if (idx_bmps == NULL) { - return false; - } - ListCell* target = NULL; - foreach (target, qry->targetList) { - TargetEntry* entry = (TargetEntry*)lfirst(target); - if (entry->resjunk) { - continue; - } - if (bms_is_member(entry->resno - FirstLowInvalidHeapAttributeNumber, idx_bmps)) { - context->isIndexedColumnUpdate = true; - } - } - bms_free(idx_bmps); - } - return false; - } - - if (IsA(node, Query)) { - /* Recurse into subselects */ - bool result = false; - context->queryNodes = lappend(context->queryNodes, (Query*)node); - context->sublevelsUp++; - result = query_tree_walker((Query*)node, (bool (*)())MotIndexedColumnUpdateWalker, (void*)context, 0); - context->sublevelsUp--; - context->queryNodes = list_delete(context->queryNodes, llast(context->queryNodes)); - return result; - } - - return expression_tree_walker(node, (bool (*)())MotIndexedColumnUpdateWalker, (void*)context); -} - -/* - * @brief: Analyze a query to check if there is update on indexed column of MOT table. - * @input: Query to be analyzed. - * @output: True/false. - */ -bool CheckMotIndexedColumnUpdate(Query* qry) -{ - UpdateDetectorContext context; - context.queryNodes = NIL; - context.sublevelsUp = 0; - context.isIndexedColumnUpdate = false; - - /* check root node RTEs in case of non RangeTblRef nodes */ - List* rtable = qry->rtable; - ListCell* lc = NULL; - foreach (lc, rtable) { - RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc); - if (qry->commandType == CMD_UPDATE && rte->rtekind == RTE_RELATION && rte->relkind == RELKIND_FOREIGN_TABLE && - isMOTFromTblOid(rte->relid)) { - Relation rel = relation_open(rte->relid, AccessShareLock); - Bitmapset* idx_bmps = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL); - relation_close(rel, NoLock); - if (idx_bmps == NULL) { - return false; - } - ListCell* target = NULL; - foreach (target, qry->targetList) { - TargetEntry* entry = (TargetEntry*)lfirst(target); - /* Junk entry is temporary and won't affect the db. in addition, entry->resno might be wrong. - * We should ignore it. - * More info in src/backend/executor/execJunk.cpp - */ - if (entry->resjunk) { - continue; - } - if (bms_is_member(entry->resno - FirstLowInvalidHeapAttributeNumber, idx_bmps)) { - bms_free(idx_bmps); - return true; - } - } - bms_free(idx_bmps); - } - } - - /* add root query to query stack list */ - context.queryNodes = lappend(context.queryNodes, qry); - - /* recursive walk on the query */ - (void)query_or_expression_tree_walker((Node*)qry, (bool (*)())MotIndexedColumnUpdateWalker, (void*)&context, 0); - - return context.isIndexedColumnUpdate; -} #endif static void CheckUnsupportInsertSelectClause(Query* query) diff --git a/src/common/backend/parser/parse_relation.cpp b/src/common/backend/parser/parse_relation.cpp index 8ac7eebc2..32bc762ff 100755 --- a/src/common/backend/parser/parse_relation.cpp +++ b/src/common/backend/parser/parse_relation.cpp @@ -57,6 +57,9 @@ #include "optimizer/planner.h" #include "storage/tcap.h" #include "gs_ledger/ledger_utils.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_def.h" +#endif #define MAXSTRLEN ((1 << 11) - 1) static RangeTblEntry* scanNameSpaceForRefname(ParseState* pstate, const char* refname, int location); @@ -1008,6 +1011,11 @@ Relation parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockm rel = HeapOpenrvExtended(relation, lockmode, true, isSupportSynonym, &detailInfo); if (rel == NULL) { /* Report some error and detail info if detailInfo has some messages. */ +#ifdef ENABLE_MOT + if (u_sess->mot_cxt.jit_compile_depth > 0) { + u_sess->mot_cxt.jit_parse_error = MOT_JIT_TABLE_NOT_FOUND; + } +#endif if (relation->schemaname) { if (IS_PGXC_DATANODE) { /* diff --git a/src/common/backend/utils/adt/pgstatfuncs.cpp b/src/common/backend/utils/adt/pgstatfuncs.cpp index c374bc33c..f7f2633a8 100644 --- a/src/common/backend/utils/adt/pgstatfuncs.cpp +++ b/src/common/backend/utils/adt/pgstatfuncs.cpp @@ -261,6 +261,8 @@ extern Datum pv_session_memctx_detail(PG_FUNCTION_ARGS); extern Datum pv_total_memory_detail(PG_FUNCTION_ARGS); extern Datum mot_global_memory_detail(PG_FUNCTION_ARGS); extern Datum mot_local_memory_detail(PG_FUNCTION_ARGS); +extern Datum mot_jit_detail(PG_FUNCTION_ARGS); +extern Datum mot_jit_profile(PG_FUNCTION_ARGS); extern Datum gs_total_nodegroup_memory_detail(PG_FUNCTION_ARGS); @@ -8267,6 +8269,190 @@ Datum mot_global_memory_detail(PG_FUNCTION_ARGS) #endif } +/* + * Description: Produce a view to show all JIT compilation information + */ +Datum mot_jit_detail(PG_FUNCTION_ARGS) +{ +#ifndef ENABLE_MOT + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("This function is not supported in cluster mode."))); + PG_RETURN_NULL(); +#else + FuncCallContext* funcctx = NULL; + MotJitDetail* entry = NULL; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) { + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* build tupdesc for result tuples */ + tupdesc = CreateTemplateTupleDesc(NUM_MOT_JIT_DETAIL_ELEM, false); + + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "proc_oid", OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "query", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "namespace", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "jittable_status", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "valid_status", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "last_updated", TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "plan_type", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "codegen_time", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "verify_time", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "finalize_time", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "compile_time", INT8OID, -1, 0); + + /* complete descriptor of the tupledesc */ + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* total number of tuples to be returned */ + funcctx->user_fctx = (void *)GetMotJitDetail(&(funcctx->max_calls)); + + (void)MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + entry = (MotJitDetail *)funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) { + Datum values[NUM_MOT_JIT_DETAIL_ELEM]; + bool nulls[NUM_MOT_JIT_DETAIL_ELEM] = {false}; + HeapTuple tuple = NULL; + + /* + * Form tuple with appropriate data. + */ + errno_t rc = 0; + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls)); + securec_check(rc, "\0", "\0"); + + entry += funcctx->call_cntr; + + /* Locking is probably not really necessary */ + values[0] = ObjectIdGetDatum(entry->procOid); + values[1] = CStringGetTextDatum(entry->query); + values[2] = CStringGetTextDatum(entry->nameSpace); + values[3] = CStringGetTextDatum(entry->jittableStatus); + values[4] = CStringGetTextDatum(entry->validStatus); + values[5] = TimestampTzGetDatum(entry->lastUpdatedTimestamp); + values[6] = CStringGetTextDatum(entry->planType); + values[7] = Int64GetDatum(entry->codegenTime); + values[8] = Int64GetDatum(entry->verifyTime); + values[9] = Int64GetDatum(entry->finalizeTime); + values[10] = Int64GetDatum(entry->compileTime); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } else { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +#endif +} + +/* + * Description: Produce a view to show profile data of MOT jitted functions and queries + */ +Datum mot_jit_profile(PG_FUNCTION_ARGS) +{ +#ifndef ENABLE_MOT + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("This function is not supported in cluster mode."))); + PG_RETURN_NULL(); +#else + FuncCallContext* funcctx = NULL; + MotJitProfile* entry = NULL; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) { + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* build tupdesc for result tuples */ + tupdesc = CreateTemplateTupleDesc(NUM_MOT_JIT_PROFILE_ELEM, false); + + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "proc_oid", OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "id", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "parent_id", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "query", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "namespace", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "weight", FLOAT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "total", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "self", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "child_gross", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "child_net", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "def_vars", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "init_vars", INT8OID, -1, 0); + + /* complete descriptor of the tupledesc */ + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* total number of tuples to be returned */ + funcctx->user_fctx = (void *)GetMotJitProfile(&(funcctx->max_calls)); + + (void)MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + entry = (MotJitProfile *)funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) { + Datum values[NUM_MOT_JIT_PROFILE_ELEM]; + bool nulls[NUM_MOT_JIT_PROFILE_ELEM] = {false}; + HeapTuple tuple = NULL; + + /* + * Form tuple with appropriate data. + */ + errno_t rc = 0; + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls)); + securec_check(rc, "\0", "\0"); + + entry += funcctx->call_cntr; + + /* Locking is probably not really necessary */ + values[0] = ObjectIdGetDatum(entry->procOid); + values[1] = Int32GetDatum(entry->id); + values[2] = Int32GetDatum(entry->parentId); + values[3] = CStringGetTextDatum(entry->query); + values[4] = CStringGetTextDatum(entry->nameSpace); + values[5] = Float4GetDatum(entry->weight); + values[6] = Int64GetDatum(entry->totalTime); + values[7] = Int64GetDatum(entry->selfTime); + values[8] = Int64GetDatum(entry->childGrossTime); + values[9] = Int64GetDatum(entry->childNetTime); + values[10] = Int64GetDatum(entry->defVarsTime); + values[11] = Int64GetDatum(entry->initVarsTime); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } else { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +#endif +} + /* * Description: Produce a view to show all local memory usage on node */ diff --git a/src/common/backend/utils/cache/plancache.cpp b/src/common/backend/utils/cache/plancache.cpp index 881fa4c64..40209f6e7 100644 --- a/src/common/backend/utils/cache/plancache.cpp +++ b/src/common/backend/utils/cache/plancache.cpp @@ -293,6 +293,7 @@ CachedPlanSource* CreateCachedPlan(Node* raw_parse_tree, const char* query_strin #ifdef ENABLE_MOT plansource->storageEngineType = SE_TYPE_UNSPECIFIED; + plansource->checkedMotJitCodegen = false; plansource->mot_jit_context = NULL; #endif @@ -375,6 +376,7 @@ CachedPlanSource* CreateOneShotCachedPlan(Node* raw_parse_tree, const char* quer #ifdef ENABLE_MOT plansource->storageEngineType = SE_TYPE_UNSPECIFIED; + plansource->checkedMotJitCodegen = false; plansource->mot_jit_context = NULL; #endif @@ -715,6 +717,86 @@ void ReleaseGenericPlan(CachedPlanSource* plansource) } } +#ifdef ENABLE_MOT +void MotTryRevalidateJitContext(CachedPlanSource* plansource) +{ + // while pending for recompilation to finish we keep the opfusion object as-is (using FDW) + JitExec::JitContextState currState = JitExec::GetJitContextState(plansource->mot_jit_context); + if (currState == JitExec::JIT_CONTEXT_STATE_READY) { + return; + } + + u_sess->mot_cxt.jit_codegen_error = 0; + bool destroyContext = false; + bool destroyOpfusion = false; + if (currState == JitExec::JIT_CONTEXT_STATE_PENDING) { + // if opfusion object needs to be destroyed we do not return early + if (plansource->opFusionObj == NULL) { + return; + } + destroyOpfusion = true; + } else if (currState == JitExec::JIT_CONTEXT_STATE_ERROR) { + destroyContext = true; + destroyOpfusion = true; + } else { + // if compilation is done, or the context is invalid we need to revalidate it + if ((currState == JitExec::JIT_CONTEXT_STATE_DONE) || (currState == JitExec::JIT_CONTEXT_STATE_INVALID)) { + Oid funcId = InvalidOid; + TransactionId funcXmin = InvalidTransactionId; + (void)JitExec::IsInvokeQueryPlan(plansource, &funcId, &funcXmin); + (void)JitExec::TryRevalidateJitContext(plansource->mot_jit_context, funcXmin); + } + + // if we arrive to error state we destroy everything, otherwise, if we have an opfusion + // object, and the next state is not ready, then we must recreate Opfusion object + // another case is when we don't have opfusion object and the next state is ready (so the + // plansource->is_checked_opfusion must be set to false + JitExec::JitContextState nextState = JitExec::GetJitContextState(plansource->mot_jit_context); + if (nextState == JitExec::JIT_CONTEXT_STATE_ERROR) { + destroyContext = true; + destroyOpfusion = true; + } else if ((plansource->opFusionObj != NULL) && (nextState != JitExec::JIT_CONTEXT_STATE_READY)) { + destroyOpfusion = true; + } else if ((plansource->opFusionObj == NULL) && (nextState == JitExec::JIT_CONTEXT_STATE_READY)) { + destroyOpfusion = true; + } + } + + if (destroyContext) { + if (plansource->mot_jit_context != nullptr) { + JitExec::DestroyJitContext(plansource->mot_jit_context, true); + plansource->mot_jit_context = nullptr; + } + plansource->checkedMotJitCodegen = false; + } + + if (destroyOpfusion) { + // we must also destroy the opfusion object (which relies on the JIT context), and have it recreated + if (!plansource->gpc.status.InShareTable()) { + if (plansource->opFusionObj != NULL) { + OpFusion *opfusion = (OpFusion *)plansource->opFusionObj; + if (opfusion->m_local.m_portalName == NULL || + OpFusion::locateFusion(opfusion->m_local.m_portalName) == NULL) { + OpFusion::tearDown(opfusion); + } else { + opfusion->m_global->m_psrc = NULL; + } + plansource->opFusionObj = NULL; + } + plansource->is_checked_opfusion = false; + } + } + + if (u_sess->mot_cxt.jit_codegen_error == ERRCODE_QUERY_CANCELED) { + // If JIT revalidation failed due to cancel request, we need to ereport. JIT source will be in error state, + // but checkedMotJitCodegen will still be false so that the JIT compilation will be triggered on next + // attempt. + Assert(!plansource->checkedMotJitCodegen); + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to user request"))); + } +} +#endif + /* * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree. * @@ -799,6 +881,38 @@ List* RevalidateCachedQuery(CachedPlanSource* plansource, bool has_lp) if (plansource->is_valid) { AcquirePlannerLocks(plansource->query_list, true); +#ifdef ENABLE_MOT + /* + * In case the plan is valid, We must revalidate MOT JIT context early + * enough before success is declared, but after plan locks are taken. + * We want to make sure that in case JIT context was invalidated and + * cannot be revalidated, then the opfusion object should be discarded, + * since it must be recreated, this time without JIT. + * This use case can happen with select from stored procedure, where + * some sub-query was invalidated, or even the stored procedure itself + * was modified. + * ATTENTION: This case relates only to MOT tables, since we have a + * ready MOT JIT context. + */ + if (plansource->is_valid) { + if (plansource->mot_jit_context != nullptr) { + MotTryRevalidateJitContext(plansource); + } else { + bool checkMotJitCodegen = (!IS_PGXC_COORDINATOR && (u_sess->SPI_cxt._connected == -1) && + (plansource->checkedMotJitCodegen == false)); + if (checkMotJitCodegen) { + // generate JIT code here + if ((plansource->storageEngineType == SE_TYPE_MOT || + plansource->storageEngineType == SE_TYPE_UNSPECIFIED) && JitExec::IsMotCodegenEnabled()) { + // MOT LLVM - due to plan invalidation we might need to re-create JIT context + TryMotJitCodegenQuery(plansource->query_string, plansource, NULL); + } + plansource->checkedMotJitCodegen = true; + } + } + } +#endif + /* * By now, if any invalidation has happened, the inval callback * functions will have marked the query invalid. @@ -814,6 +928,19 @@ List* RevalidateCachedQuery(CachedPlanSource* plansource, bool has_lp) Assert(!plansource->gpc.status.InShareTable()); +#ifdef ENABLE_MOT + /* + * CachedPlanSource is no longer valid. Invalidate MOT JIT context forcefully, + * so that it will be re-validated in the next execution. + * ATTENTION: This case relates only to MOT tables, since we have a + * ready MOT JIT context. + */ + if (plansource->mot_jit_context != nullptr) { + JitExec::ForceJitContextInvalidation(plansource->mot_jit_context); + } + plansource->checkedMotJitCodegen = false; +#endif + /* * Discard the no-longer-useful query tree. (Note: we don't want to do * this any earlier, else we'd not have been able to release locks @@ -1007,14 +1134,6 @@ List* RevalidateCachedQuery(CachedPlanSource* plansource, bool has_lp) } plansource->is_checked_opfusion = false; -#ifdef ENABLE_MOT - /* clean JIT context if exists */ - if (plansource->mot_jit_context) { - JitExec::DestroyJitContext(plansource->mot_jit_context); - plansource->mot_jit_context = NULL; - } -#endif - /* * Note: we do not reset generic_cost or total_custom_cost, although we * could choose to do so. If the DDL or statistics change that prompted @@ -1204,9 +1323,6 @@ CachedPlan* BuildCachedPlan(CachedPlanSource* plansource, List* qlist, ParamList */ snapshot_set = false; if (!ActiveSnapshotSet() && -#ifdef ENABLE_MOT - !(plansource->storageEngineType == SE_TYPE_MOT) && -#endif analyze_requires_snapshot(plansource->raw_parse_tree)) { PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); snapshot_set = true; @@ -1386,6 +1502,12 @@ CachedPlan* BuildCachedPlan(CachedPlanSource* plansource, List* qlist, ParamList plpgsql_estate = saved_estate; u_sess->opt_cxt.nextval_default_expr_type = plansource->nextval_default_expr_type; +#ifdef ENABLE_MOT + /* Set plan storageEngineType and mot_jit_context */ + plan->storageEngineType = plansource->storageEngineType; + plan->mot_jit_context = plansource->mot_jit_context; +#endif + return plan; } @@ -1876,6 +1998,12 @@ CachedPlan* GetCachedPlan(CachedPlanSource* plansource, ParamListInfo boundParam plan = plansource->gplan; Assert(plan->magic == CACHEDPLAN_MAGIC); +#ifdef ENABLE_MOT + /* MOT JIT context might have got revalidated and destroyed, so we fetch it again from plan source. */ + plan->storageEngineType = plansource->storageEngineType; + plan->mot_jit_context = plansource->mot_jit_context; +#endif + /* Update soft parse counter for Unique SQL */ UniqueSQLStatCountSoftParse(1); } else { @@ -2052,9 +2180,8 @@ CachedPlan* GetCachedPlan(CachedPlanSource* plansource, ParamListInfo boundParam } #ifdef ENABLE_MOT - /* set plan storageEngineType */ - plan->storageEngineType = plansource->storageEngineType; - plan->mot_jit_context = plansource->mot_jit_context; + Assert(plan->storageEngineType == plansource->storageEngineType); + Assert(plan->mot_jit_context == plansource->mot_jit_context); #endif ListCell *lc; @@ -2494,6 +2621,7 @@ CachedPlanSource* CopyCachedPlan(CachedPlanSource* plansource, bool is_share) #ifdef ENABLE_MOT newsource->storageEngineType = SE_TYPE_UNSPECIFIED; + newsource->checkedMotJitCodegen = false; newsource->mot_jit_context = NULL; #endif @@ -3113,10 +3241,13 @@ void ResetPlanCache(void) { #ifdef ENABLE_MOT /* MOT: clean any JIT context */ - if (plansource->mot_jit_context) { - JitExec::DestroyJitContext(plansource->mot_jit_context); + if (plansource->mot_jit_context != NULL) { + if (!JitExec::IsJitSubContext(plansource->mot_jit_context)) { + JitExec::DestroyJitContext(plansource->mot_jit_context, true); + } plansource->mot_jit_context = NULL; } + plansource->checkedMotJitCodegen = false; #endif if (plansource->lightProxyObj != NULL) { diff --git a/src/common/backend/utils/errcodes.txt b/src/common/backend/utils/errcodes.txt index 05e732597..c1a32e917 100644 --- a/src/common/backend/utils/errcodes.txt +++ b/src/common/backend/utils/errcodes.txt @@ -506,7 +506,8 @@ HV030 E ERRCODE_FDW_KEY_SIZE_EXCEEDS_MAX_ALLOWED fdw HV031 E ERRCODE_FDW_DDL_IN_TRANSACTION_NOT_ALLOWED fdw_ddl_in_transaction_not_allowed HV032 E ERRCODE_FDW_TOO_MANY_INDEX_COLUMNS fdw_too_many_index_columns HV033 E ERRCODE_FDW_INDEX_ON_NULLABLE_COLUMN_NOT_ALLOWED fdw_index_on_nullable_column_not_allowed -HV034 E ERRCODE_FDW_TOO_MANY_DDL_CHANGES_IN_TRANSACTION_NOT_ALLOWED fdw_too_many_ddl_statements_in_transaction +HV034 E ERRCODE_FDW_TOO_MANY_DDL_CHANGES_IN_TRANSACTION fdw_too_many_ddl_statements_in_transaction +HV035 E ERRCODE_FDW_DROP_INDEXED_COLUMN_NOT_ALLOWED fdw_drop_indexed_column_not_allowed Section: Class P0 - PL/pgSQL Error diff --git a/src/common/pl/plpgsql/src/pl_exec.cpp b/src/common/pl/plpgsql/src/pl_exec.cpp index 92e2a57db..66314f199 100644 --- a/src/common/pl/plpgsql/src/pl_exec.cpp +++ b/src/common/pl/plpgsql/src/pl_exec.cpp @@ -65,6 +65,11 @@ #include "access/hash.h" #include "distributelayer/streamMain.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#include "storage/mot/mot_fdw.h" +#endif + extern bool checkRecompileCondition(CachedPlanSource* plansource); static const char* const raise_skip_msg = "RAISE"; typedef struct { @@ -105,7 +110,6 @@ typedef struct SimpleEcontextStackEntry { * Local function forward declarations ************************************************************/ static void plpgsql_exec_error_callback(void* arg); -extern PLpgSQL_datum* copy_plpgsql_datum(PLpgSQL_datum* datum); static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block); static int exec_stmts(PLpgSQL_execstate* estate, List* stmts); @@ -146,8 +150,6 @@ static int exchange_parameters( PLpgSQL_execstate* estate, PLpgSQL_stmt_dynexecute* dynstmt, List* stmts, int* ppdindex, int* datumindex); static bool is_anonymous_block(const char* query); static int exec_stmt_dynfors(PLpgSQL_execstate* estate, PLpgSQL_stmt_dynfors* stmt); -static void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi); -void exec_eval_cleanup(PLpgSQL_execstate* estate); static void exec_prepare_plan(PLpgSQL_execstate* estate, PLpgSQL_expr* expr, int cursorOptions); static bool exec_simple_check_node(Node* node); @@ -170,12 +172,10 @@ static int exec_run_select(PLpgSQL_execstate* estate, PLpgSQL_expr* expr, long m Portal* portalP, bool isCollectParam = false); static int exec_for_query(PLpgSQL_execstate* estate, PLpgSQL_stmt_forq* stmt, Portal portal, bool prefetch_ok, int dno); static ParamListInfo setup_param_list(PLpgSQL_execstate* estate, PLpgSQL_expr* expr); -static void plpgsql_param_fetch(ParamListInfo params, int paramid); static void exec_move_row(PLpgSQL_execstate* estate, PLpgSQL_rec* rec, PLpgSQL_row* row, HeapTuple tup, TupleDesc tupdesc, bool fromExecSql = false); static void exec_read_bulk_collect(PLpgSQL_execstate *estate, PLpgSQL_row *row, SPITupleTable *tuptab); static char* convert_value_to_string(PLpgSQL_execstate* estate, Datum value, Oid valtype); -static Datum pl_coerce_type_typmod(Datum value, Oid targetTypeId, int32 targetTypMod); static Datum exec_cast_value(PLpgSQL_execstate* estate, Datum value, Oid valtype, Oid reqtype, FmgrInfo* reqinput, Oid reqtypioparam, int32 reqtypmod, bool isnull); static Datum exec_simple_cast_value( @@ -192,7 +192,6 @@ static void exec_set_sql_notfound(PLpgSQL_execstate* estate, PLpgSQL_state state static void exec_set_sql_isopen(PLpgSQL_execstate* estate, bool state); static void exec_set_sql_rowcount(PLpgSQL_execstate* estate, int rowcount); static void plpgsql_create_econtext(PLpgSQL_execstate *estate, MemoryContext saveCxt = NULL); -static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate); static void free_var(PLpgSQL_var* var); static PreparedParamsData* exec_eval_using_params(PLpgSQL_execstate* estate, List* params); static void free_params_data(PreparedParamsData* ppd); @@ -218,7 +217,6 @@ static int check_line_validity_in_for_query(PLpgSQL_stmt_forq* stmt, int, int); static void BindCursorWithPortal(Portal portal, PLpgSQL_execstate *estate, int varno); static char* transformAnonymousBlock(char* query); -static bool needRecompilePlan(SPIPlanPtr plan); static void rebuild_exception_subtransaction_chain(PLpgSQL_execstate* estate, List* transactionList); @@ -2991,6 +2989,11 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) /* Run the block's statements */ rc = exec_stmts(estate, block->body); +#ifdef ENABLE_MOT + // throws ereport + MOTCheckTransactionAborted(); +#endif + estate->err_text = gettext_noop("during statement block exit"); /* @@ -3066,6 +3069,12 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) #ifndef ENABLE_MULTIPLE_NODES } #endif + +#ifdef ENABLE_MOT + // throws ereport + MOTCheckTransactionAborted(); +#endif + stp_retore_old_xact_stmt_state(savedisAllowCommitRollback); u_sess->SPI_cxt.is_stp = savedIsSTP; u_sess->SPI_cxt.is_proconfig_set = savedProConfigIsSet; @@ -3269,6 +3278,10 @@ static int exec_stmts(PLpgSQL_execstate* estate, List* stmts) int rc = exec_stmt(estate, stmt); stmtid++; +#ifdef ENABLE_MOT + CallXactCallbacks(XACT_EVENT_STMT_FINISH); +#endif + if (rc == PLPGSQL_RC_GOTO_UNRESOLVED) { /* * If the under layer returns an unresolved GOTO(not found), we need @@ -5475,7 +5488,7 @@ static int exec_stmt_raise(PLpgSQL_execstate* estate, PLpgSQL_stmt_raise* stmt) * Initialize a mostly empty execution state * ---------- */ -static void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi) +void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi) { /* this link will be restored at exit from plpgsql_call_handler */ func->cur_estate = estate; @@ -5695,7 +5708,6 @@ static int exec_stmt_execsql(PLpgSQL_execstate* estate, PLpgSQL_stmt_execsql* st foreach (l2, plansource->query_list) { Query* q = (Query*)lfirst(l2); - AssertEreport(IsA(q, Query), MOD_PLSQL, "Query is required."); if (q->canSetTag) { if (q->commandType == CMD_INSERT || q->commandType == CMD_UPDATE || q->commandType == CMD_DELETE || @@ -10352,7 +10364,7 @@ static ParamListInfo setup_param_list(PLpgSQL_execstate* estate, PLpgSQL_expr* e /* * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch */ -static void plpgsql_param_fetch(ParamListInfo params, int paramid) +void plpgsql_param_fetch(ParamListInfo params, int paramid) { int dno; PLpgSQL_execstate* estate = NULL; @@ -11293,7 +11305,7 @@ static char* convert_value_to_string(PLpgSQL_execstate* estate, Datum value, Oid * @IN targetTypMod: target type mode. * @return: Datum - the result of specified typmode. */ -static Datum pl_coerce_type_typmod(Datum value, Oid targetTypeId, int32 targetTypMod) +Datum pl_coerce_type_typmod(Datum value, Oid targetTypeId, int32 targetTypMod) { CoercionPathType pathtype; Oid funcId; @@ -12184,7 +12196,7 @@ static void plpgsql_create_econtext(PLpgSQL_execstate* estate, MemoryContext sav * We check that it matches the top stack entry, and destroy the stack * entry along with the context. */ -static void plpgsql_destroy_econtext(PLpgSQL_execstate* estate) +void plpgsql_destroy_econtext(PLpgSQL_execstate* estate) { SimpleEcontextStackEntry *pre = NULL; SimpleEcontextStackEntry *plcontext = u_sess->plsql_cxt.simple_econtext_stack; @@ -12231,10 +12243,10 @@ void plpgsql_xact_cb(XactEvent event, void* arg) { #ifdef ENABLE_MOT /* - * XACT_EVENT_PREROLLBACK_CLEANUP is added only for MOT FDW to cleanup some - * internal resources. So, others can safely ignore this event. + * These events are added only for MOT FDW. So, others can safely ignore these event. */ - if (event == XACT_EVENT_PREROLLBACK_CLEANUP) { + if (event == XACT_EVENT_START || event == XACT_EVENT_RECORD_COMMIT || event == XACT_EVENT_PREROLLBACK_CLEANUP || + event == XACT_EVENT_POST_COMMIT_CLEANUP || event == XACT_EVENT_STMT_FINISH) { return; } #endif @@ -12640,7 +12652,7 @@ static char* transformAnonymousBlock(char* query) * True : need to redo Prepare proc before exec stored procedure. * False: could execute SP execution directly. */ -static bool needRecompilePlan(SPIPlanPtr plan) +bool needRecompilePlan(SPIPlanPtr plan) { bool ret_val = false; ListCell* l = NULL; diff --git a/src/common/pl/plpgsql/src/pl_handler.cpp b/src/common/pl/plpgsql/src/pl_handler.cpp index b47a211ea..6948ac084 100755 --- a/src/common/pl/plpgsql/src/pl_handler.cpp +++ b/src/common/pl/plpgsql/src/pl_handler.cpp @@ -1057,11 +1057,22 @@ Datum plpgsql_inline_handler(PG_FUNCTION_ARGS) * Connect to SPI manager */ SPI_STACK_LOG("connect", NULL, NULL); +#ifdef ENABLE_MOT + bool needSPIFinish = true; +#endif rc = SPI_connect_ext(DestSPI, NULL, NULL, codeblock->atomic ? 0 : SPI_OPT_NONATOMIC); if (rc != SPI_OK_CONNECT) { - ereport(ERROR, (errmodule(MOD_PLSQL), - errcode(ERRCODE_SPI_CONNECTION_FAILURE), - errmsg("SPI_connect failed: %s when execute anonymous block.", SPI_result_code_string(rc)))); +#ifdef ENABLE_MOT + if (rc != SPI_ERROR_CONNECT) { +#endif + ereport(ERROR, (errmodule(MOD_PLSQL), + errcode(ERRCODE_SPI_CONNECTION_FAILURE), + errmsg("SPI_connect failed: %s when execute anonymous block.", SPI_result_code_string(rc)))); +#ifdef ENABLE_MOT + } else { + needSPIFinish = false; + } +#endif } PGSTAT_START_PLSQL_TIME_RECORD(); @@ -1188,11 +1199,17 @@ Datum plpgsql_inline_handler(PG_FUNCTION_ARGS) * Disconnect from SPI manager */ SPI_STACK_LOG("finish", NULL, NULL); - if (((rc = SPI_finish()) != SPI_OK_FINISH)) { - ereport(ERROR, (errmodule(MOD_PLSQL), - errcode(ERRCODE_SPI_CONNECTION_FAILURE), - errmsg("SPI_finish failed: %s when execute anonymous block.", SPI_result_code_string(rc)))); +#ifdef ENABLE_MOT + if (needSPIFinish) { +#endif + if (((rc = SPI_finish()) != SPI_OK_FINISH)) { + ereport(ERROR, (errmodule(MOD_PLSQL), + errcode(ERRCODE_SPI_CONNECTION_FAILURE), + errmsg("SPI_finish failed: %s when execute anonymous block.", SPI_result_code_string(rc)))); + } +#ifdef ENABLE_MOT } +#endif return retval; } diff --git a/src/gausskernel/CMakeLists.txt b/src/gausskernel/CMakeLists.txt index 5c98c8ac3..0003a3263 100755 --- a/src/gausskernel/CMakeLists.txt +++ b/src/gausskernel/CMakeLists.txt @@ -225,6 +225,7 @@ if("${ENABLE_MOT}" STREQUAL "ON") $ $ $ + $ $ $ $ diff --git a/src/gausskernel/optimizer/commands/analyze.cpp b/src/gausskernel/optimizer/commands/analyze.cpp index 28d144bcc..1354ff318 100755 --- a/src/gausskernel/optimizer/commands/analyze.cpp +++ b/src/gausskernel/optimizer/commands/analyze.cpp @@ -905,10 +905,9 @@ HeapTuple* get_total_rows(Relation onerel, VacuumStmt* vacstmt, BlockNumber relp onerel, vacstmt, elevel, rows, target_rows, totalrows, totaldeadrows, vacattrstats, attr_cnt); } else if (isForeignTable || onerel->rd_rel->relkind == RELKIND_STREAM || (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && -#ifdef ENABLE_MOT - (isMOTFromTblOid(RelationGetRelid(onerel)) || isOracleFDWFromTblOid(RelationGetRelid(onerel)) || -#else (isOracleFDWFromTblOid(RelationGetRelid(onerel)) || +#ifdef ENABLE_MOT + isMOTFromTblOid(RelationGetRelid(onerel)) || #endif isPostgresFDWFromTblOid(RelationGetRelid(onerel))))) { /* diff --git a/src/gausskernel/optimizer/commands/functioncmds.cpp b/src/gausskernel/optimizer/commands/functioncmds.cpp index f37bb6182..40b6ac80a 100644 --- a/src/gausskernel/optimizer/commands/functioncmds.cpp +++ b/src/gausskernel/optimizer/commands/functioncmds.cpp @@ -88,6 +88,9 @@ #include "tsearch/ts_type.h" #include "commands/comment.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif typedef struct PendingLibraryDelete { char* filename; /* library file name. */ @@ -1389,6 +1392,15 @@ void RemoveFunctionById(Oid funcOid) Form_pg_proc procedureStruct = (Form_pg_proc)GETSTRUCT(tup); isagg = procedureStruct->proisagg; +#ifdef ENABLE_MOT + char* funcName = pstrdup(NameStr(procedureStruct->proname)); + bool isNull = false; + Datum prokindDatum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_prokind, &isNull); + bool proIsProcedure = isNull ? false : PROC_IS_PRO(CharGetDatum(prokindDatum)); + Datum packageDatum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_package, &isNull); + bool isPackage = isNull ? false : DatumGetBool(packageDatum); +#endif + if (procedureStruct->prolang == ClanguageId) { PrepareCFunctionLibrary(tup); } @@ -1424,6 +1436,13 @@ void RemoveFunctionById(Oid funcOid) } DropErrorByOid(PLPGSQL_PROC, funcOid); ce_cache_refresh_type |= 0x20; /* refresh proc cache */ + +#ifdef ENABLE_MOT + if (proIsProcedure && !isPackage && JitExec::IsMotSPCodegenEnabled()) { + JitExec::PurgeJitSourceCache(funcOid, JitExec::JIT_PURGE_SCOPE_SP, JitExec::JIT_PURGE_EXPIRE, funcName); + } + pfree_ext(funcName); +#endif } /* * Guts of function deletion. diff --git a/src/gausskernel/optimizer/commands/prepare.cpp b/src/gausskernel/optimizer/commands/prepare.cpp index 19825e1cf..0ad19ac57 100755 --- a/src/gausskernel/optimizer/commands/prepare.cpp +++ b/src/gausskernel/optimizer/commands/prepare.cpp @@ -49,6 +49,9 @@ #endif #include "replication/walreceiver.h" #include "optimizer/gplanmgr.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif #define CLUSTER_EXPANSION_BASE 2 @@ -67,6 +70,69 @@ static void CopyPlanForGPCIfNecessary(CachedPlanSource* psrc, Portal portal) } } +#ifdef ENABLE_MOT +void TryMotJitCodegenQuery(const char* queryString, CachedPlanSource* psrc, Query* query) +{ + // Try to generate LLVM jitted code - first cleanup jit of previous run. + if (psrc->mot_jit_context != NULL) { + if (JitExec::IsJitContextPendingCompile(psrc->mot_jit_context) || + JitExec::IsJitContextDoneCompile(psrc->mot_jit_context)) { + return; + } + + // NOTE: context is cleaned up during end of session, this should not happen, + // maybe a warning should be issued + Assert(false); + ereport(WARNING, (errmsg("Cached Plan Source already has a MOT JIT Context, destroying the residual context"))); + JitExec::DestroyJitContext(psrc->mot_jit_context, true); + psrc->mot_jit_context = NULL; + Assert(psrc->opFusionObj == NULL); + } + + if (query == NULL) { + if (list_length(psrc->query_list) != 1) { + elog(DEBUG2, "Plan source does not have exactly one query"); + return; + } + query = (Query*)linitial(psrc->query_list); + if (query == NULL) { + elog(DEBUG2, "No query object present for MOT JIT"); + return; + } + } + + if ((query->commandType != CMD_SELECT) && (query->commandType != CMD_INSERT) && + (query->commandType != CMD_UPDATE) && (query->commandType != CMD_DELETE)) { + elog(DEBUG2, "Query is not SELECT|INSERT|UPDATE|DELETE"); + return; + } + + if (JitExec::IsMotCodegenPrintEnabled()) { + elog(LOG, "Attempting to generate MOT jitted code for query: %s\n", queryString); + } + + Assert(psrc->opFusionObj == NULL && psrc->mot_jit_context == NULL); + u_sess->mot_cxt.jit_codegen_error = 0; + psrc->mot_jit_context = JitExec::TryJitCodegenQuery(query, queryString); + if (psrc->mot_jit_context != NULL) { + if (JitExec::IsJitContextValid(psrc->mot_jit_context)) { + psrc->is_checked_opfusion = false; + } + } else { + if (JitExec::IsMotCodegenPrintEnabled()) { + elog(LOG, "Failed to generate jitted MOT function for query %s\n", queryString); + } + if (u_sess->mot_cxt.jit_codegen_error == ERRCODE_QUERY_CANCELED) { + // If JIT compilation failed due to cancel request, we need to ereport. JIT source will be in error state, + // but checkedMotJitCodegen will still be false so that the JIT compilation will be triggered on next + // attempt. + Assert(!psrc->checkedMotJitCodegen); + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to user request"))); + } + } +} +#endif + /* * Implements the 'PREPARE' utility statement. */ @@ -134,6 +200,21 @@ void PrepareQuery(PrepareStmt* stmt, const char* queryString) query = parse_analyze_varparams((Node*)copyObject(stmt->query), queryString, &argtypes, &nargs); +#ifdef ENABLE_MOT + /* check cross engine queries */ + StorageEngineType storageEngineType = SE_TYPE_UNSPECIFIED; + CheckTablesStorageEngine(query, &storageEngineType); + SetCurrentTransactionStorageEngine(storageEngineType); + /* set the plan's storage engine */ + plansource->storageEngineType = storageEngineType; + + /* gpc does not support MOT engine */ + if (ENABLE_CN_GPC && plansource->gpc.status.IsSharePlan() && + (storageEngineType == SE_TYPE_MOT || storageEngineType == SE_TYPE_MIXED)) { + plansource->gpc.status.SetKind(GPC_UNSHARED); + } +#endif + if (ENABLE_CN_GPC && plansource->gpc.status.IsSharePlan() && contains_temp_tables(query->rtable)) { /* temp table unsupport shared */ plansource->gpc.status.SetKind(GPC_UNSHARED); @@ -191,6 +272,15 @@ void PrepareQuery(PrepareStmt* stmt, const char* queryString) * Save the results. */ StorePreparedStatement(stmt->name, plansource, true); + +#ifdef ENABLE_MOT + // Try MOT JIT code generation only after the plan source is saved. + if ((plansource->storageEngineType == SE_TYPE_MOT || plansource->storageEngineType == SE_TYPE_UNSPECIFIED) && + !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { + // MOT JIT code generation + TryMotJitCodegenQuery(queryString, plansource, query); + } +#endif } /* @@ -245,6 +335,19 @@ void ExecuteQuery(ExecuteStmt* stmt, IntoClause* intoClause, const char* querySt OpFusion::clearForCplan((OpFusion*)psrc->opFusionObj, psrc); +#ifdef ENABLE_MOT + /* + * MOT JIT Execution: + * Assist in distinguishing query boundaries in case of range query when client uses batches. This allows us to + * know a new query started, and in case a previous execution did not fetch all records (since user is working in + * batch-mode, and can decide to quit fetching in the middle), using this information we can infer this is a new + * scan, and old scan state should be discarded. + */ + if (psrc->mot_jit_context != NULL) { + JitResetScan(psrc->mot_jit_context); + } +#endif + if (psrc->opFusionObj != NULL) { Assert(psrc->cplan == NULL); (void)RevalidateCachedQuery(psrc); diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index 2919f68c0..cd55aa051 100644 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -5815,6 +5815,30 @@ void renameatt(RenameStmt* stmt) errmsg("Column: %s has bound some masking policies, can not be renamed.", stmt->subname), errdetail("cannot rename masking column"))); } + +#ifdef ENABLE_MOT + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) { + RenameForeingTableCmd cmd = { + T_RenameForeingTableCmd, + relid, + stmt->renameType, + stmt->subname, + stmt->newname + }; + FdwRoutine* fdwroutine; + + if (rel->rd_fdwroutine != nullptr) { + fdwroutine = rel->rd_fdwroutine; + } else { + fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel)); + } + + if (fdwroutine->ValidateTableDef != nullptr) { + fdwroutine->ValidateTableDef((Node*)&cmd); + } + } +#endif + relation_close(rel, AccessShareLock); renameatt_internal(relid, @@ -6317,6 +6341,23 @@ void RenameRelation(RenameStmt* stmt) rename_hist_by_usertable(relid, stmt->newname); } +#ifdef ENABLE_MOT + if (stmt->renameType == OBJECT_INDEX) { + Oid relOid = IndexGetRelation(relid, false); + Relation rel = RelationIdGetRelation(relOid); + if (RelationIsForeignTable(rel) && isMOTFromTblOid(RelationGetRelid(rel))) { + FdwRoutine* fdwroutine = rel->rd_fdwroutine; + if (fdwroutine == nullptr) { + fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel)); + } + if (fdwroutine->ValidateTableDef != nullptr) { + fdwroutine->ValidateTableDef((Node*)stmt); + } + } + RelationClose(rel); + } +#endif + /* Do the work */ RenameRelationInternal(relid, stmt->newname); /* @@ -9820,6 +9861,23 @@ static FORCE_INLINE void ATExecAppendDefValExpr(_in_ AttrNumber attnum, _in_ Exp tab->rewrite = true; } +#ifdef ENABLE_MOT +static void ATExecMOTAlterTable(AlterForeingTableCmd* cmd) +{ + FdwRoutine* fdwroutine; + + if (cmd->rel->rd_fdwroutine != nullptr) { + fdwroutine = cmd->rel->rd_fdwroutine; + } else { + fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(cmd->rel)); + } + + if (fdwroutine->ValidateTableDef != nullptr) { + fdwroutine->ValidateTableDef((Node*)cmd); + } +} +#endif + static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, ColumnDef* colDef, bool isOid, bool recurse, bool recursing, LOCKMODE lockmode) { @@ -10102,8 +10160,14 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, * case we mustn't invoke Phase 3 on a view or foreign table, since they * have no storage. */ +#ifdef ENABLE_MOT + if ((relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel)) && attribute.attnum > 0) || + (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_FOREIGN_TABLE && + relkind != RELKIND_STREAM && relkind != RELKIND_CONTQUERY && attribute.attnum > 0)) { +#else if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_FOREIGN_TABLE && relkind != RELKIND_STREAM && relkind != RELKIND_CONTQUERY && attribute.attnum > 0) { +#endif /* test whether new column is null or not*/ bool testNotNull = colDef->is_not_null; @@ -10220,6 +10284,21 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid); add_column_collation_dependency(myrelid, newattnum, attribute.attcollation); +#ifdef ENABLE_MOT + if (relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) { + AlterForeingTableCmd fcmd = { + T_AlterForeingTableCmd, + AT_AddColumn, + rel, + nullptr, + (Node*)colDef, + typeOid, + defval + }; + ATExecMOTAlterTable(&fcmd); + } +#endif + #ifdef ENABLE_MULTIPLE_NODES if (unlikely(RelationIsTsStore(rel))) { /* add col to tag table */ @@ -11242,6 +11321,22 @@ static void ATExecDropColumn(List** wqueue, Relation rel, const char* colName, D CStoreRelDropColumn(rel, attnum, rel->rd_rel->relowner); } ResetTempAutoIncrement(rel, attnum); + +#ifdef ENABLE_MOT + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) { + AlterForeingTableCmd fcmd = { + T_AlterForeingTableCmd, + AT_DropColumn, + rel, + colName, + nullptr, + InvalidOid, + nullptr + }; + ATExecMOTAlterTable(&fcmd); + } +#endif + #ifdef ENABLE_MULTIPLE_NODES if (unlikely(RelationIsTsStore(rel))) { /* drop column in tag table */ @@ -19130,7 +19225,8 @@ static void RangeVarCallbackForAlterRelation( #ifdef ENABLE_MOT if (isMOTFromTblOid(relid)) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is a mot, which does not support alter table.", rv->relname))); + errmsg("\"%s\" is not a table", rv->relname), + errhint("Use ALTER FOREIGN TABLE to alter a foreign table."))); } else { #endif ereport(ERROR, @@ -21534,6 +21630,22 @@ static void ATExecUnusableIndex(Relation rel) // the index is already lock by AccessExclusive lock, do not lock again. // AccessExclusiveLock on heap already held by call AlterTableLookupRelation(). heapRelation = relation_open(heapOid, NoLock); + +#ifdef ENABLE_MOT + if (heapRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(heapOid)) { + AlterForeingTableCmd fcmd = { + T_AlterForeingTableCmd, + AT_UnusableIndex, + heapRelation, + nullptr, + nullptr, + InvalidOid, + nullptr + }; + ATExecMOTAlterTable(&fcmd); + } +#endif + // call the internal function, update pg_index system table ATExecSetIndexUsableState(IndexRelationId, rel->rd_id, false); diff --git a/src/gausskernel/process/postmaster/pgstat.cpp b/src/gausskernel/process/postmaster/pgstat.cpp index 8a8d14ce3..eeda60480 100644 --- a/src/gausskernel/process/postmaster/pgstat.cpp +++ b/src/gausskernel/process/postmaster/pgstat.cpp @@ -92,6 +92,9 @@ #include "instruments/instr_slow_query.h" #include "instruments/instr_statement.h" #include "instruments/instr_handle_mgr.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif #ifdef ENABLE_UT #define static @@ -7959,6 +7962,44 @@ MotMemoryDetail* GetMotMemoryDetail(uint32* num, bool isGlobal) return returnDetailArray; } + +MotJitDetail* GetMotJitDetail(uint32* num) +{ + MotJitDetail* returnDetailArray = NULL; + + *num = 0; + + // Ensure that MOT FDW routine and Xact callbacks are registered. + if (!u_sess->mot_cxt.callbacks_set) { + ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false); + if (fdw != NULL) { + (void)GetFdwRoutine(fdw->fdwhandler); + } + } + + returnDetailArray = JitExec::MOTGetJitDetail(num); + + return returnDetailArray; +} + +MotJitProfile* GetMotJitProfile(uint32* num) +{ + MotJitProfile* returnProfileArray = NULL; + + *num = 0; + + // Ensure that MOT FDW routine and Xact callbacks are registered. + if (!u_sess->mot_cxt.callbacks_set) { + ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false); + if (fdw != NULL) { + (void)GetFdwRoutine(fdw->fdwhandler); + } + } + + returnProfileArray = JitExec::MOTGetJitProfile(num); + + return returnProfileArray; +} #endif int64 getCpuTime(void) diff --git a/src/gausskernel/process/postmaster/postmaster.cpp b/src/gausskernel/process/postmaster/postmaster.cpp index 6dafee00f..a97ebf55b 100644 --- a/src/gausskernel/process/postmaster/postmaster.cpp +++ b/src/gausskernel/process/postmaster/postmaster.cpp @@ -2337,6 +2337,12 @@ int PostmasterMain(int argc, char* argv[]) RemovePgTempFiles(); RemoveErrorCacheFiles(); + +#ifdef ENABLE_MOT + /* Initialize the MOT engine */ + InitMOT(); +#endif + /* * Remember postmaster startup time */ @@ -8519,7 +8525,9 @@ void ExitPostmaster(int status) } #ifdef ENABLE_MOT - TermMOT(); /* shutdown memory engine before codegen is destroyed */ + if (g_instance.status != ImmediateShutdown) { + TermMOT(); /* shutdown memory engine before codegen is destroyed */ + } #endif if (ENABLE_THREAD_POOL_DN_LOGICCONN) { @@ -13972,4 +13980,4 @@ void SSRestartFailoverPromote() pmState = PM_WAIT_BACKENDS; SShandle_promote_signal(); -} \ No newline at end of file +} diff --git a/src/gausskernel/process/postmaster/startup.cpp b/src/gausskernel/process/postmaster/startup.cpp index 49bf166b2..f051c7c1a 100755 --- a/src/gausskernel/process/postmaster/startup.cpp +++ b/src/gausskernel/process/postmaster/startup.cpp @@ -34,9 +34,6 @@ #include "storage/pmsignal.h" #include "storage/proc.h" #include "utils/guc.h" -#ifdef ENABLE_MOT -#include "storage/mot/mot_fdw.h" -#endif #include "gssignal/gs_signal.h" #include "access/parallel_recovery/dispatcher.h" @@ -363,17 +360,6 @@ void StartupProcessMain(void) StartupDummyStandby(); } else { on_shmem_exit(StartupReleaseAllLocks, 0); - -#ifdef ENABLE_MOT - /* - * Init MOT first - */ - InitMOT(); - - /* - * MOT recovery is part of StartupXlog - */ -#endif DeleteDisConnFileInClusterStandby(); if (!dummyStandbyMode) { Assert(g_instance.startup_cxt.badPageHashTbl == NULL); diff --git a/src/gausskernel/process/tcop/postgres.cpp b/src/gausskernel/process/tcop/postgres.cpp index 90bccae11..179c76c54 100755 --- a/src/gausskernel/process/tcop/postgres.cpp +++ b/src/gausskernel/process/tcop/postgres.cpp @@ -1269,9 +1269,7 @@ PlannedStmt* pg_plan_query(Query* querytree, int cursorOptions, ParamListInfo bo /* Planner must have a snapshot in case it calls user-defined functions. */ if (!GTM_LITE_MODE) { /* Skip if GTMLite */ -#ifndef ENABLE_MOT /* To support skipping snapshot in case of MOT tables. */ Assert(ActiveSnapshotSet()); -#endif } TRACE_POSTGRESQL_QUERY_PLAN_START(); @@ -2645,16 +2643,6 @@ static void exec_simple_query(const char* query_string, MessageType messageType, } SetCurrentTransactionStorageEngine(storageEngineType); - if (!IsTransactionExitStmt(parsetree) && storageEngineType == SE_TYPE_MIXED) { - ereport(ERROR, (errcode(ERRCODE_FDW_CROSS_STORAGE_ENGINE_QUERY_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Cross storage engine query is not supported"))); - } - - if (!IsTransactionExitStmt(parsetree) && IsMixedEngineUsed()) { - ereport(ERROR, (errcode(ERRCODE_FDW_CROSS_STORAGE_ENGINE_TRANSACTION_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Cross storage engine transaction is not supported"))); - } - /* block MOT engine queries in sub-transactions */ if (!IsTransactionExitStmt(parsetree) && IsMOTEngineUsedInParentTransaction() && IsMOTEngineUsed()) { ereport(ERROR, (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), errmodule(MOD_MOT), @@ -2666,13 +2654,6 @@ static void exec_simple_query(const char* query_string, MessageType messageType, ereport(ERROR, (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), errmodule(MOD_MOT), errmsg("Explicit prepare transaction is not supported for memory table"))); } - - /* check for MOT update of indexed field. Can check only the querytree head, no need for drill down */ - if (!IsTransactionExitStmt(parsetree) && - (querytree_list != NULL && CheckMotIndexedColumnUpdate((Query*)linitial(querytree_list)))) { - ereport(ERROR, (errcode(ERRCODE_FDW_UPDATE_INDEXED_FIELD_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Update of indexed column is not supported for memory table"))); - } #endif /* Try using light proxy to execute query */ @@ -3310,30 +3291,6 @@ static void exec_plan_with_params(StringInfo input_message) } #endif -#ifdef ENABLE_MOT -static void TryMotJitCodegenQuery(const char* queryString, CachedPlanSource* psrc, Query* query) -{ - // Try to generate LLVM jitted code - first cleanup jit of previous run. - if (psrc->mot_jit_context != NULL) { - // NOTE: context is cleaned up during end of session, this should not happen, - // maybe a warning should be issued - psrc->mot_jit_context = NULL; - } - - if (JitExec::IsMotCodegenPrintEnabled()) { - elog(LOG, "Attempting to generate MOT jitted code for query: %s\n", queryString); - } - - JitExec::JitPlan* jitPlan = JitExec::IsJittable(query, queryString); - if (jitPlan != NULL) { - psrc->mot_jit_context = JitExec::JitCodegenQuery(query, queryString, jitPlan); - if ((psrc->mot_jit_context == NULL) && JitExec::IsMotCodegenPrintEnabled()) { - elog(LOG, "Failed to generate jitted MOT function for query %s\n", queryString); - } - } -} -#endif - #ifdef ENABLE_MULTIPLE_NODES /* get param oid from paramTypeNames, and save into paramTypes. */ static void GetParamOidFromName(char** paramTypeNames, Oid* paramTypes, int numParams) @@ -3372,6 +3329,7 @@ static void exec_parse_message(const char* query_string, /* string to execute */ List* parsetree_list = NULL; Node* raw_parse_tree = NULL; const char* commandTag = NULL; + Query* query = NULL; List* querytree_list = NULL; CachedPlanSource* psrc = NULL; bool is_named = false; @@ -3555,7 +3513,6 @@ static void exec_parse_message(const char* query_string, /* string to execute */ (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot insert multiple commands into a prepared statement"))); if (parsetree_list != NIL) { - Query* query = NULL; bool snapshot_set = false; int i; @@ -3629,24 +3586,11 @@ static void exec_parse_message(const char* query_string, /* string to execute */ /* set the plan's storage engine */ psrc->storageEngineType = storageEngineType; - if (!IsTransactionExitStmt(raw_parse_tree) && storageEngineType == SE_TYPE_MIXED) { - ereport(ERROR, (errcode(ERRCODE_FDW_CROSS_STORAGE_ENGINE_QUERY_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Cross storage engine query is not supported"))); - } - - if (psrc->storageEngineType == SE_TYPE_MOT && !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { - // MOT LLVM - TryMotJitCodegenQuery(query_string, psrc, query); - } /* gpc does not support MOT engine */ if (ENABLE_CN_GPC && psrc->gpc.status.IsSharePlan() && - (psrc->storageEngineType == SE_TYPE_MOT || psrc->storageEngineType == SE_TYPE_MIXED)) { + (storageEngineType == SE_TYPE_MOT || storageEngineType == SE_TYPE_MIXED)) { psrc->gpc.status.SetKind(GPC_UNSHARED); } - if (!IsTransactionExitStmt(raw_parse_tree) && CheckMotIndexedColumnUpdate(query)) { - ereport(ERROR, (errcode(ERRCODE_FDW_UPDATE_INDEXED_FIELD_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Update of indexed column is not supported for memory table"))); - } #endif if (ENABLE_CN_GPC && psrc->gpc.status.IsSharePlan() && contains_temp_tables(query->rtable)) { @@ -3795,6 +3739,15 @@ static void exec_parse_message(const char* query_string, /* string to execute */ u_sess->pcache_cxt.unnamed_stmt_psrc = psrc; } +#ifdef ENABLE_MOT + // Try MOT JIT code generation only after the plan source is saved. + if (query != NULL && (psrc->storageEngineType == SE_TYPE_MOT || psrc->storageEngineType == SE_TYPE_UNSPECIFIED) && + !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { + // MOT JIT code generation + TryMotJitCodegenQuery(query_string, psrc, query); + } +#endif + MemoryContextSwitchTo(oldcontext); pass_parsing: @@ -3895,12 +3848,7 @@ static int getSingleNodeIdx(StringInfo input_message, CachedPlanSource* psrc, co * snapshot active till we're done, so that plancache.c doesn't have to * take new ones. */ -#ifdef ENABLE_MOT - if (!(psrc->storageEngineType == SE_TYPE_MOT) && !GTM_LITE_MODE && - (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree))) { -#else if (!GTM_LITE_MODE && (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree))) { -#endif PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } @@ -4445,10 +4393,6 @@ static void exec_bind_message(StringInfo input_message) #ifdef ENABLE_MOT /* set transaction storage engine and check for cross transaction violation */ SetCurrentTransactionStorageEngine(psrc->storageEngineType); - if (!IsTransactionExitStmt(psrc->raw_parse_tree) && IsMixedEngineUsed()) { - ereport(ERROR, (errcode(ERRCODE_FDW_CROSS_STORAGE_ENGINE_TRANSACTION_NOT_SUPPORTED), errmodule(MOD_MOT), - errmsg("Cross storage engine transaction is not supported"))); - } /* block MOT engine queries in sub-transactions */ if (!IsTransactionExitStmt(psrc->raw_parse_tree) && IsMOTEngineUsedInParentTransaction() && IsMOTEngineUsed()) { @@ -4694,12 +4638,7 @@ static void exec_bind_message(StringInfo input_message) * snapshot active till we're done, so that plancache.c doesn't have to * take new ones. */ -#ifdef ENABLE_MOT - if (!(psrc->storageEngineType == SE_TYPE_MOT) && - (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree))) { -#else if (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree)) { -#endif PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } @@ -5753,6 +5692,10 @@ void finish_xact_command(void) ereport(DEBUG3, (errmsg_internal("CommitTransactionCommand"))); } +#ifdef ENABLE_MOT + CallXactCallbacks(XACT_EVENT_STMT_FINISH); +#endif + CommitTransactionCommand(); #ifdef MEMORY_CONTEXT_CHECKING @@ -10608,6 +10551,19 @@ static void exec_one_in_batch(CachedPlanSource* psrc, ParamListInfo params, int OpFusion::clearForCplan((OpFusion*)psrc->opFusionObj, psrc); +#ifdef ENABLE_MOT + /* + * MOT JIT Execution: + * Assist in distinguishing query boundaries in case of range query when client uses batches. This allows us to + * know a new query started, and in case a previous execution did not fetch all records (since user is working in + * batch-mode, and can decide to quit fetching in the middle), using this information we can infer this is a new + * scan, and old scan state should be discarded. + */ + if (psrc->mot_jit_context != NULL) { + JitResetScan(psrc->mot_jit_context); + } +#endif + if (psrc->opFusionObj != NULL) { (void)RevalidateCachedQuery(psrc); OpFusion *opFusionObj = (OpFusion *)(psrc->opFusionObj); @@ -10696,19 +10652,6 @@ static void exec_one_in_batch(CachedPlanSource* psrc, ParamListInfo params, int portal->stmts = *gpcCopyStmts; } -#ifdef ENABLE_MOT - /* - * MOT JIT Execution: - * Assist in distinguishing query boundaries in case of range query when client uses batches. This allows us to - * know a new query started, and in case a previous execution did not fetch all records (since user is working in - * batch-mode, and can decide to quit fetching in the middle), using this information we can infer this is a new - * scan, and old scan state should be discarded. - */ - if (psrc->mot_jit_context != NULL) { - JitResetScan(psrc->mot_jit_context); - } -#endif - bool checkSQLBypass = IS_PGXC_DATANODE && !psrc->gpc.status.InShareTable() && (psrc->cplan == NULL) && (psrc->is_checked_opfusion == false); if (checkSQLBypass) { diff --git a/src/gausskernel/process/tcop/pquery.cpp b/src/gausskernel/process/tcop/pquery.cpp index b135b4340..2cf99847b 100644 --- a/src/gausskernel/process/tcop/pquery.cpp +++ b/src/gausskernel/process/tcop/pquery.cpp @@ -77,7 +77,7 @@ extern CmdType set_cmd_type(const char* commandTag); #ifdef ENABLE_MOT QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, DestReceiver* dest, ParamListInfo params, int instrument_options, - JitExec::JitContext* mot_jit_context /* = nullptr */) + JitExec::MotJitContext* motJitContext /* = nullptr */) #else QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, DestReceiver* dest, ParamListInfo params, int instrument_options) @@ -107,7 +107,11 @@ QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Sna qd->totaltime = NULL; qd->executed = false; #ifdef ENABLE_MOT - qd->mot_jit_context = mot_jit_context; + if (motJitContext != nullptr && JitExec::IsJitContextValid(motJitContext)) { + qd->mot_jit_context = motJitContext; + } else { + qd->mot_jit_context = nullptr; + } #endif return qd; @@ -165,7 +169,7 @@ void FreeQueryDesc(QueryDesc* qdesc) * MOT LLVM */ static void ProcessMotJitQuery(PlannedStmt* plan, const char* sourceText, ParamListInfo params, - JitExec::JitContext* motJitContext, char* completionTag) + JitExec::MotJitContext* motJitContext, char* completionTag) { Oid lastOid = InvalidOid; uint64 tuplesProcessed = 0; @@ -224,7 +228,7 @@ static void ProcessMotJitQuery(PlannedStmt* plan, const char* sourceText, ParamL */ #ifdef ENABLE_MOT static void ProcessQuery(PlannedStmt* plan, const char* sourceText, ParamListInfo params, bool isMOTTable, - JitExec::JitContext* motJitContext, DestReceiver* dest, char* completionTag) + JitExec::MotJitContext* motJitContext, DestReceiver* dest, char* completionTag) #else static void ProcessQuery( PlannedStmt* plan, const char* sourceText, ParamListInfo params, DestReceiver* dest, char* completionTag) @@ -235,18 +239,15 @@ static void ProcessQuery( elog(DEBUG3, "ProcessQuery"); #ifdef ENABLE_MOT - Snapshot snap = InvalidSnapshot; + Snapshot snap = GetActiveSnapshot(); // Check for snapshot before calling ProcessMotJitQuery - if (isMOTTable && motJitContext && !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { + if (isMOTTable && motJitContext && JitExec::IsJitContextValid(motJitContext) && + !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { // MOT LLVM ProcessMotJitQuery(plan, sourceText, params, motJitContext, completionTag); return; } - if (!isMOTTable) { - snap = GetActiveSnapshot(); - } - /* * Create the QueryDesc object */ @@ -686,28 +687,22 @@ void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snaps case PORTAL_ONE_SELECT: { ps = (PlannedStmt*)linitial(portal->stmts); - /* Must set snapshot before starting executor, unless it is a query with only MOT Tables. */ -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - if (snapshot) { - PushActiveSnapshot(snapshot); + /* Must set snapshot before starting executor. */ + if (snapshot) { + PushActiveSnapshot(snapshot); + } else { + if (u_sess->pgxc_cxt.gc_fdw_snapshot) { + PushActiveSnapshot(u_sess->pgxc_cxt.gc_fdw_snapshot); } else { - if (u_sess->pgxc_cxt.gc_fdw_snapshot) { - PushActiveSnapshot(u_sess->pgxc_cxt.gc_fdw_snapshot); - } else { - bool force_local_snapshot = false; + bool force_local_snapshot = false; - if (portal->cplan != NULL && portal->cplan->single_shard_stmt) { - /* with single shard, we will be forced to do local snapshot work */ - force_local_snapshot = true; - } - PushActiveSnapshot(GetTransactionSnapshot(force_local_snapshot)); + if (portal->cplan != NULL && portal->cplan->single_shard_stmt) { + /* with single shard, we will be forced to do local snapshot work */ + force_local_snapshot = true; } + PushActiveSnapshot(GetTransactionSnapshot(force_local_snapshot)); } -#ifdef ENABLE_MOT } -#endif /* * For operator track of active SQL, explain performance is triggered for SELECT SQL, @@ -719,20 +714,15 @@ void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snaps } #ifdef ENABLE_MOT - Snapshot tempSnap = InvalidSnapshot; - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { - tempSnap = GetActiveSnapshot(); - } - - JitExec::JitContext* mot_jit_context = + JitExec::MotJitContext* mot_jit_context = (portal->cplan != NULL) ? portal->cplan->mot_jit_context : nullptr; /* * Create QueryDesc in portal's context; for the moment, set * the destination to DestNone. */ - queryDesc = CreateQueryDesc( - ps, portal->sourceText, tempSnap, InvalidSnapshot, None_Receiver, params, 0, mot_jit_context); + queryDesc = CreateQueryDesc(ps, portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, + params, 0, mot_jit_context); #else /* * Create QueryDesc in portal's context; for the moment, set @@ -808,13 +798,7 @@ void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snaps portal->portalPos = 0; portal->posOverflow = false; -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - PopActiveSnapshot(); -#ifdef ENABLE_MOT - } -#endif + PopActiveSnapshot(); break; } case PORTAL_ONE_RETURNING: @@ -1404,13 +1388,7 @@ static uint64 PortalRunSelect(Portal portal, bool forward, long count, DestRecei else nprocessed = RunFromStore(portal, direction, count, dest); } else { -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - PushActiveSnapshot(queryDesc->snapshot); -#ifdef ENABLE_MOT - } -#endif + PushActiveSnapshot(queryDesc->snapshot); #ifdef PGXC if (portal->name != NULL && portal->name[0] != '\0' && IsA(queryDesc->planstate, RemoteQueryState)) { @@ -1444,13 +1422,7 @@ static uint64 PortalRunSelect(Portal portal, bool forward, long count, DestRecei } nprocessed = queryDesc->estate->es_processed; -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - PopActiveSnapshot(); -#ifdef ENABLE_MOT - } -#endif + PopActiveSnapshot(); } if (!ScanDirectionIsNoMovement(direction)) { @@ -1482,22 +1454,10 @@ static uint64 PortalRunSelect(Portal portal, bool forward, long count, DestRecei if (portal->holdStore) nprocessed = RunFromStore(portal, direction, count, dest); else { -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - PushActiveSnapshot(queryDesc->snapshot); -#ifdef ENABLE_MOT - } -#endif + PushActiveSnapshot(queryDesc->snapshot); ExecutorRun(queryDesc, direction, count); nprocessed = queryDesc->estate->es_processed; -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { -#endif - PopActiveSnapshot(); -#ifdef ENABLE_MOT - } -#endif + PopActiveSnapshot(); } if (!ScanDirectionIsNoMovement(direction)) { @@ -1733,12 +1693,7 @@ static void PortalRunUtility(Portal portal, Node* utilityStmt, bool isTopLevel, * say, it has to update an index with expressions that invoke * user-defined functions, then it had better have a snapshot. */ -#ifdef ENABLE_MOT - if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT) && - !(IsA(utilityStmt, TransactionStmt) || IsA(utilityStmt, LockStmt) || IsA(utilityStmt, VariableSetStmt) || -#else if (!(IsA(utilityStmt, TransactionStmt) || IsA(utilityStmt, LockStmt) || IsA(utilityStmt, VariableSetStmt) || -#endif IsA(utilityStmt, VariableShowStmt) || IsA(utilityStmt, ConstraintsSetStmt) || /* efficiency hacks from here down */ IsA(utilityStmt, FetchStmt) || IsA(utilityStmt, ListenStmt) || IsA(utilityStmt, NotifyStmt) || @@ -1844,7 +1799,7 @@ static void PortalRunMulti( Node* stmt = (Node*)lfirst(stmtlist_item); #ifdef ENABLE_MOT bool isMOTTable = false; - JitExec::JitContext* mot_jit_context = nullptr; + JitExec::MotJitContext* mot_jit_context = nullptr; #endif /* @@ -1866,26 +1821,22 @@ static void PortalRunMulti( PGSTAT_START_TIME_RECORD(); /* - * Must always have a snapshot for plannable queries, unless it is a MOT query. + * Must always have a snapshot for plannable queries. * First time through, take a new snapshot; for subsequent queries in the * same portal, just update the snapshot's copy of the command * counter. */ + if (!active_snapshot_set) { + PushActiveSnapshot(GetTransactionSnapshot(force_local_snapshot)); + active_snapshot_set = true; + } else + UpdateActiveSnapshotCommandId(); + #ifdef ENABLE_MOT if ((portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) { isMOTTable = true; mot_jit_context = portal->cplan->mot_jit_context; } - - if (!isMOTTable) { -#endif - if (!active_snapshot_set) { - PushActiveSnapshot(GetTransactionSnapshot(force_local_snapshot)); - active_snapshot_set = true; - } else - UpdateActiveSnapshotCommandId(); -#ifdef ENABLE_MOT - } #endif if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) diff --git a/src/gausskernel/process/threadpool/knl_instance.cpp b/src/gausskernel/process/threadpool/knl_instance.cpp index 7814d7f7f..c3ee37d05 100755 --- a/src/gausskernel/process/threadpool/knl_instance.cpp +++ b/src/gausskernel/process/threadpool/knl_instance.cpp @@ -678,7 +678,7 @@ static void knl_g_archive_thread_info_init(knl_g_archive_thread_info *archive_th #ifdef ENABLE_MOT static void knl_g_mot_init(knl_g_mot_context* mot_cxt) { - mot_cxt->jitExecMode = JitExec::JIT_EXEC_MODE_INVALID; + mot_cxt->shmemVariableCache = NULL; } #endif diff --git a/src/gausskernel/process/threadpool/knl_session.cpp b/src/gausskernel/process/threadpool/knl_session.cpp index 3b4d059cb..9d330c745 100755 --- a/src/gausskernel/process/threadpool/knl_session.cpp +++ b/src/gausskernel/process/threadpool/knl_session.cpp @@ -1315,7 +1315,7 @@ static void knl_u_ps_init(knl_u_ps_context* ps_cxt) } #ifdef ENABLE_MOT -static void knl_u_mot_init(knl_u_mot_context* mot_cxt) +void knl_u_mot_init(knl_u_mot_context* mot_cxt) { Assert(mot_cxt != NULL); mot_cxt->callbacks_set = false; @@ -1328,11 +1328,15 @@ static void knl_u_mot_init(knl_u_mot_context* mot_cxt) mot_cxt->jit_llvm_if_stack = NULL; mot_cxt->jit_llvm_while_stack = NULL; mot_cxt->jit_llvm_do_while_stack = NULL; - mot_cxt->jit_tvm_if_stack = NULL; - mot_cxt->jit_tvm_while_stack = NULL; - mot_cxt->jit_tvm_do_while_stack = NULL; mot_cxt->jit_context = NULL; mot_cxt->jit_txn = NULL; + mot_cxt->jit_compile_depth = 0; + mot_cxt->jit_parse_error = 0; + mot_cxt->jit_pg_query = NULL; + mot_cxt->jit_ns_stack = NULL; + mot_cxt->jit_session_source_map = NULL; + mot_cxt->jit_xact_callback_registered = false; + mot_cxt->jit_codegen_error = 0; } #endif diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index 0d0efc19e..d44c60662 100755 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -119,7 +119,7 @@ static void ExecEndPlan(PlanState *planstate, EState *estate); static void ExecCollectMaterialForSubplan(EState *estate); #ifdef ENABLE_MOT static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, - ScanDirection direction, DestReceiver *dest, JitExec::JitContext* mot_jit_context); + ScanDirection direction, DestReceiver *dest, JitExec::MotJitContext* motJitContext); #else static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, ScanDirection direction, DestReceiver *dest); @@ -2130,7 +2130,7 @@ static void ExecCollectMaterialForSubplan(EState *estate) */ #ifdef ENABLE_MOT static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, - ScanDirection direction, DestReceiver *dest, JitExec::JitContext* motJitContext) + ScanDirection direction, DestReceiver *dest, JitExec::MotJitContext* motJitContext) #else static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, ScanDirection direction, DestReceiver *dest) @@ -2195,7 +2195,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, #ifdef ENABLE_MOT if (unlikely(recursive_early_stop)) { slot = NULL; - } else if (motJitContext && !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) { + } else if (motJitContext && JitExec::IsJitContextValid(motJitContext) && !IS_PGXC_COORDINATOR && + JitExec::IsMotCodegenEnabled()) { // MOT LLVM int scanEnded = 0; if (!motFinishedExecution) { diff --git a/src/gausskernel/runtime/executor/lightProxy.cpp b/src/gausskernel/runtime/executor/lightProxy.cpp index 52288f7e7..237668730 100644 --- a/src/gausskernel/runtime/executor/lightProxy.cpp +++ b/src/gausskernel/runtime/executor/lightProxy.cpp @@ -1140,14 +1140,8 @@ void lightProxy::runMsg(StringInfo exec_message) "commands ignored until end of transaction block, firstChar[%c]", u_sess->proc_cxt.firstChar), 0)); - /* Must set snapshot before starting executor, unless it is a MOT tables transaction. */ -#ifdef ENABLE_MOT - if (!IsMOTEngineUsed()) { -#endif - PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); -#ifdef ENABLE_MOT - } -#endif + /* Must set snapshot before starting executor. */ + PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); connect(); @@ -1201,13 +1195,7 @@ void lightProxy::runMsg(StringInfo exec_message) m_msgctl->hasResult = (m_cplan->resultDesc != NULL) ? true : false; handleResponse(); -#ifdef ENABLE_MOT - if (!IsMOTEngineUsed()) { -#endif - PopActiveSnapshot(); -#ifdef ENABLE_MOT - } -#endif + PopActiveSnapshot(); /* * We need a CommandCounterIncrement after every query, except diff --git a/src/gausskernel/runtime/executor/spi.cpp b/src/gausskernel/runtime/executor/spi.cpp index bcd2bf19e..b6f30950d 100644 --- a/src/gausskernel/runtime/executor/spi.cpp +++ b/src/gausskernel/runtime/executor/spi.cpp @@ -48,6 +48,10 @@ #include "commands/sqladvisor.h" #include "distributelayer/streamMain.h" +#ifdef ENABLE_MOT +#include "storage/mot/jit_exec.h" +#endif + THR_LOCAL uint32 SPI_processed = 0; THR_LOCAL SPITupleTable *SPI_tuptable = NULL; THR_LOCAL int SPI_result; @@ -56,6 +60,11 @@ static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamL bool isCollectParam = false); void _SPI_prepare_plan(const char *src, SPIPlanPtr plan); + +#ifdef ENABLE_MOT +static bool _SPI_prepare_plan_guarded(const char *src, SPIPlanPtr plan, parse_query_func parser); +#endif + #ifdef PGXC static void _SPI_pgxc_prepare_plan(const char *src, List *src_parsetree, SPIPlanPtr plan, parse_query_func parser); #endif @@ -77,6 +86,7 @@ static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan); static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan); +static MemoryContext _SPI_execmem(void); static MemoryContext _SPI_procmem(void); static bool _SPI_checktuples(void); extern void ClearVacuumStmt(VacuumStmt *stmt); @@ -1027,7 +1037,19 @@ SPIPlanPtr SPI_prepare_params(const char *src, ParserSetupHook parserSetup, void plan.spi_key = INVALID_SPI_KEY; plan.id = (uint32)-1; +#ifdef ENABLE_MOT + if (u_sess->mot_cxt.jit_compile_depth > 0) { + if (!_SPI_prepare_plan_guarded(src, &plan, parser)) { + _SPI_end_call(true); + SPI_result = SPI_ERROR_ARGUMENT; + return NULL; + } + } else { + _SPI_prepare_plan(src, &plan, parser); + } +#else _SPI_prepare_plan(src, &plan, parser); +#endif /* copy plan to procedure context */ SPIPlanPtr result = _SPI_make_plan_non_temp(&plan); @@ -2204,6 +2226,29 @@ void spi_printtup(TupleTableSlot *slot, DestReceiver *self) /* * Static functions */ +#ifdef ENABLE_MOT +static bool _SPI_prepare_plan_guarded(const char *src, SPIPlanPtr plan, parse_query_func parser) +{ + bool result = true; + PG_TRY(); + { + _SPI_prepare_plan(src, plan, parser); + } + PG_CATCH(); + { + // report error and reset error state, but first switch back to executor memory context + _SPI_execmem(); + ErrorData* edata = CopyErrorData(); + JitExec::JitReportParseError(edata, src); + FlushErrorState(); + FreeErrorData(edata); + result = false; + } + PG_END_TRY(); + return result; +} +#endif + /* * Parse and analyze a querystring. * @@ -2742,8 +2787,14 @@ static int _SPI_execute_plan0(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot s } #endif +#ifdef ENABLE_MOT + qdesc = CreateQueryDesc((PlannedStmt *)stmt, plansource->query_string, snap, crosscheck_snapshot, dest, + paramLI, 0, plansource->mot_jit_context); +#else qdesc = CreateQueryDesc((PlannedStmt *)stmt, plansource->query_string, snap, crosscheck_snapshot, dest, paramLI, 0); +#endif + res = _SPI_pquery(qdesc, fire_triggers, canSetTag ? tcount : 0, from_lock); ResourceOwner tmp = t_thrd.utils_cxt.CurrentResourceOwner; t_thrd.utils_cxt.CurrentResourceOwner = oldOwner; @@ -3350,6 +3401,11 @@ void _SPI_hold_cursor(bool is_rollback) _SPI_end_call(true); } +static MemoryContext _SPI_execmem(void) +{ + return MemoryContextSwitchTo(u_sess->SPI_cxt._current->execCxt); +} + static MemoryContext _SPI_procmem(void) { return MemoryContextSwitchTo(u_sess->SPI_cxt._current->procCxt); @@ -3368,7 +3424,7 @@ int _SPI_begin_call(bool execmem) } if (execmem) { /* switch to the Executor memory context */ - MemoryContextSwitchTo(u_sess->SPI_cxt._current->execCxt); + _SPI_execmem(); } return 0; diff --git a/src/gausskernel/runtime/opfusion/opfusion.cpp b/src/gausskernel/runtime/opfusion/opfusion.cpp index 371e9e00e..baadfaa09 100644 --- a/src/gausskernel/runtime/opfusion/opfusion.cpp +++ b/src/gausskernel/runtime/opfusion/opfusion.cpp @@ -271,7 +271,8 @@ FusionType OpFusion::getFusionType(CachedPlan *plan, ParamListInfo params, List FusionType result = NONE_FUSION; #ifdef ENABLE_MOT - if (plan && plan->mot_jit_context && JitExec::IsMotCodegenEnabled()) { + if (plan && plan->mot_jit_context && JitExec::IsJitContextValid(plan->mot_jit_context) && + JitExec::IsMotCodegenEnabled()) { result = GetMotFusionType(planned_stmt); } else { #endif @@ -340,17 +341,10 @@ void OpFusion::executeInit() ExecCheckXactReadOnly(m_global->m_planstmt); } -#ifdef ENABLE_MOT - if (!(u_sess->exec_cxt.CurrentOpFusionObj->m_global->m_cacheplan && - u_sess->exec_cxt.CurrentOpFusionObj->m_global->m_cacheplan->storageEngineType == SE_TYPE_MOT)) { -#endif - if (m_local.m_snapshot == NULL) { - m_local.m_snapshot = RegisterSnapshot(GetTransactionSnapshot()); - } - PushActiveSnapshot(m_local.m_snapshot); -#ifdef ENABLE_MOT + if (m_local.m_snapshot == NULL) { + m_local.m_snapshot = RegisterSnapshot(GetTransactionSnapshot()); } -#endif + PushActiveSnapshot(m_local.m_snapshot); } void OpFusion::auditRecord() @@ -386,24 +380,17 @@ void OpFusion::auditRecord() bool OpFusion::executeEnd(const char *portal_name, bool *isQueryCompleted) { -#ifdef ENABLE_MOT - if (!(u_sess->exec_cxt.CurrentOpFusionObj->m_global->m_cacheplan && - u_sess->exec_cxt.CurrentOpFusionObj->m_global->m_cacheplan->storageEngineType == SE_TYPE_MOT)) { -#endif - opfusion_executeEnd(m_global->m_planstmt, - ((m_global->m_psrc == NULL) ? NULL : (m_global->m_psrc->query_string)), GetActiveSnapshot()); - const char *query_string = t_thrd.postgres_cxt.debug_query_string; - if (query_string == NULL && m_global->m_psrc != NULL) { - query_string = m_global->m_psrc->query_string; - } - if (m_local.m_ledger_hash_exist && query_string != NULL) { - opfusion_ledger_ExecutorEnd(m_local.m_optype, m_global->m_reloid, query_string, m_local.m_ledger_relhash); - } - - PopActiveSnapshot(); -#ifdef ENABLE_MOT + opfusion_executeEnd(m_global->m_planstmt, + ((m_global->m_psrc == NULL) ? NULL : (m_global->m_psrc->query_string)), GetActiveSnapshot()); + const char *query_string = t_thrd.postgres_cxt.debug_query_string; + if (query_string == NULL && m_global->m_psrc != NULL) { + query_string = m_global->m_psrc->query_string; } -#endif + if (m_local.m_ledger_hash_exist && query_string != NULL) { + opfusion_ledger_ExecutorEnd(m_local.m_optype, m_global->m_reloid, query_string, m_local.m_ledger_relhash); + } + + PopActiveSnapshot(); #ifdef MEMORY_CONTEXT_CHECKING /* Check all memory contexts when executor starts */ diff --git a/src/gausskernel/runtime/opfusion/opfusion_mot.cpp b/src/gausskernel/runtime/opfusion/opfusion_mot.cpp index 5f9af98c9..c9b2ba0f3 100644 --- a/src/gausskernel/runtime/opfusion/opfusion_mot.cpp +++ b/src/gausskernel/runtime/opfusion/opfusion_mot.cpp @@ -37,6 +37,8 @@ MotJitSelectFusion::MotJitSelectFusion( old_context = MemoryContextSwitchTo(m_global->m_context); InitGlobals(); MemoryContextSwitchTo(old_context); + } else { + m_c_global = ((MotJitSelectFusion*)(psrc->opFusionObj))->m_c_global; } old_context = MemoryContextSwitchTo(m_local.m_localContext); InitLocals(params); @@ -49,10 +51,25 @@ void MotJitSelectFusion::InitGlobals() m_global->m_tupDesc = ExecCleanTypeFromTL(m_global->m_planstmt->planTree->targetlist, false); } else { ereport(ERROR, - (errmodule(MOD_EXECUTOR), - errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), - errmsg("unrecognized node type: %d when executing executor node.", - (int)nodeTag(m_global->m_planstmt->planTree)))); + (errmodule(MOD_EXECUTOR), + errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), + errmsg("unrecognized node type: %d when executing executor node.", + (int)nodeTag(m_global->m_planstmt->planTree)))); + } + m_c_global = (MotJitSelectFusionGlobalVariable*)palloc0(sizeof(MotJitSelectFusionGlobalVariable)); + + m_c_global->m_limitCount = -1; + m_c_global->m_limitOffset = -1; + /* get limit num */ + if (IsA(m_global->m_planstmt->planTree, Limit)) { + Limit* limit = (Limit*)m_global->m_planstmt->planTree; + if (limit->limitCount != NULL && IsA(limit->limitCount, Const) && !((Const*)limit->limitCount)->constisnull) { + m_c_global->m_limitCount = DatumGetInt64(((Const*)limit->limitCount)->constvalue); + } + if (limit->limitOffset != NULL && IsA(limit->limitOffset, Const) && + !((Const*)limit->limitOffset)->constisnull) { + m_c_global->m_limitOffset = DatumGetInt64(((Const*)limit->limitOffset)->constvalue); + } } } @@ -70,39 +87,45 @@ bool MotJitSelectFusion::execute(long max_rows, char* completionTag) bool success = false; setReceiver(); unsigned long nprocessed = 0; - bool finish = false; int rc = 0; - while (!finish) { + int64 start_row = 0; + int64 get_rows = 0; + + start_row = m_c_global->m_limitOffset >= 0 ? m_c_global->m_limitOffset : start_row; + get_rows = m_c_global->m_limitCount >= 0 ? (m_c_global->m_limitCount + start_row) : max_rows; + + while (nprocessed < (unsigned long)get_rows) { uint64_t tpProcessed = 0; int scanEnded = 0; - rc = JitExec::JitExecQuery(m_global->m_cacheplan->mot_jit_context, params, m_local.m_reslot, - &tpProcessed, &scanEnded); - if (scanEnded || (tpProcessed == 0) || (rc != 0)) { - // raise flag so that next round we will bail out (current tuple still must be reported to user) - finish = true; - } + rc = JitExec::JitExecQuery( + m_global->m_cacheplan->mot_jit_context, params, m_local.m_reslot, &tpProcessed, &scanEnded); CHECK_FOR_INTERRUPTS(); if (tpProcessed > 0) { nprocessed++; (*m_local.m_receiver->receiveSlot)(m_local.m_reslot, m_local.m_receiver); (void)ExecClearTuple(m_local.m_reslot); - if ((max_rows != FETCH_ALL) && (nprocessed == (unsigned long)max_rows)) { - finish = true; - } + } + if (scanEnded || (tpProcessed == 0) || (rc != 0)) { + break; } } - success = true; + if (rc == 0) { + success = true; + } if (m_local.m_isInsideRec) { (*m_local.m_receiver->rDestroy)(m_local.m_receiver); } + if (t_thrd.utils_cxt.CurrentResourceOwner != m_local.m_resOwner) { + t_thrd.utils_cxt.CurrentResourceOwner = m_local.m_resOwner; + } m_local.m_position = 0; m_local.m_isCompleted = true; - errno_t errorno = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, - "SELECT %lu", nprocessed); + errno_t errorno = + snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, "SELECT %lu", nprocessed); securec_check_ss(errorno, "\0", "\0"); return success; @@ -151,36 +174,35 @@ void MotJitModifyFusion::InitLocals(ParamListInfo params) bool MotJitModifyFusion::execute(long max_rows, char* completionTag) { + errno_t ret = EOK; bool success = false; uint64_t tpProcessed = 0; int scanEnded = 0; ParamListInfo params = (m_local.m_outParams != NULL) ? m_local.m_outParams : m_local.m_params; - int rc = JitExec::JitExecQuery(m_global->m_cacheplan->mot_jit_context, params, m_local.m_reslot, - &tpProcessed, &scanEnded); + int rc = JitExec::JitExecQuery( + m_global->m_cacheplan->mot_jit_context, params, m_local.m_reslot, &tpProcessed, &scanEnded); if (rc == 0) { - (void)ExecClearTuple(m_local.m_reslot); success = true; - errno_t ret = EOK; - switch (m_c_local.m_cmdType) - { - case CMD_INSERT: - ret = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, - "INSERT 0 %lu", tpProcessed); - securec_check_ss(ret, "\0", "\0"); - break; - case CMD_UPDATE: - ret = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, - "UPDATE %lu", tpProcessed); - securec_check_ss(ret, "\0", "\0"); - break; - case CMD_DELETE: - ret = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, - "DELETE %lu", tpProcessed); - securec_check_ss(ret, "\0", "\0"); - break; - default: - break; - } + } + (void)ExecClearTuple(m_local.m_reslot); + switch (m_c_local.m_cmdType) { + case CMD_INSERT: + ret = snprintf_s( + completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, "INSERT 0 %lu", tpProcessed); + securec_check_ss(ret, "\0", "\0"); + break; + case CMD_UPDATE: + ret = snprintf_s( + completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, "UPDATE %lu", tpProcessed); + securec_check_ss(ret, "\0", "\0"); + break; + case CMD_DELETE: + ret = snprintf_s( + completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, "DELETE %lu", tpProcessed); + securec_check_ss(ret, "\0", "\0"); + break; + default: + break; } m_local.m_isCompleted = true; diff --git a/src/gausskernel/runtime/opfusion/opfusion_util.cpp b/src/gausskernel/runtime/opfusion/opfusion_util.cpp index 7490e9863..6292691c1 100644 --- a/src/gausskernel/runtime/opfusion/opfusion_util.cpp +++ b/src/gausskernel/runtime/opfusion/opfusion_util.cpp @@ -78,6 +78,14 @@ const char* getBypassReason(FusionType result) return "Bypass executed through sort fusion"; } + case MOT_JIT_SELECT_FUSION: { + return "Bypass executed through MOT JIT select fusion"; + } + + case MOT_JIT_MODIFY_FUSION: { + return "Bypass executed through MOT JIT modify fusion"; + } + case NOBYPASS_NO_SIMPLE_PLAN: { return "Bypass not executed because the plan of query is not a simple plan"; } diff --git a/src/gausskernel/storage/access/transam/xact.cpp b/src/gausskernel/storage/access/transam/xact.cpp index 6fdb04a52..9c2d596e7 100755 --- a/src/gausskernel/storage/access/transam/xact.cpp +++ b/src/gausskernel/storage/access/transam/xact.cpp @@ -1562,12 +1562,8 @@ static TransactionId RecordTransactionCommit(void) */ CallXactCallbacks(XACT_EVENT_RECORD_COMMIT); - /* - * For MOT, XLOG entries will be written in the above callback for XACT_EVENT_RECORD_COMMIT. - * So, we should re-check and update wrote_xlog accordingly. - */ - if (t_thrd.xlog_cxt.XactLastRecEnd != 0) { - wrote_xlog = true; + if (!wrote_xlog) { + Assert(t_thrd.xlog_cxt.XactLastRecEnd == 0); } #endif @@ -1659,6 +1655,14 @@ static TransactionId RecordTransactionCommit(void) * This should be done after setCommitCsn for the transaction. */ CallXactCallbacks(XACT_EVENT_RECORD_COMMIT); + + /* + * For MOT, XLOG entries will be written in the above callback for XACT_EVENT_RECORD_COMMIT. + * So, we should re-check and update wrote_xlog accordingly. + */ + if (t_thrd.xlog_cxt.XactLastRecEnd != 0) { + wrote_xlog = true; + } #endif @@ -3106,6 +3110,11 @@ static void CommitTransaction(bool STP_commit) g_instance.plan_cache->Commit(); } +#ifdef ENABLE_MOT + /* Post commit cleanup of MOT JIT sources. */ + CallXactCallbacks(XACT_EVENT_POST_COMMIT_CLEANUP); +#endif + /* * Likewise, dropping of files deleted during the transaction is best done * after releasing relcache and buffer pins. (This is not strictly @@ -7290,17 +7299,6 @@ static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, Transac } if (t_thrd.xlog_cxt.standbyState == STANDBY_DISABLED) { -#ifdef ENABLE_MOT - /* - * Report committed transaction to MOT Engine. - * If (XactMOTEngineUsed(xinfo)) - This is an optimization to avoid calling - * MOT redo commit callbacks in case of commit does not have MOT records. - * It is disabled for the time being since data used to identify storage - * engine type is cleared in 2phase commit prepare phase. - */ - CallRedoCommitCallback(xid); -#endif - /* * Mark the transaction committed in pg_xact. We don't bother updating * pg_csnlog during replay. @@ -7334,17 +7332,6 @@ static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, Transac } else { CSNLogRecordAssignedTransactionId(max_xid); -#ifdef ENABLE_MOT - /* - * Report committed transaction to MOT Engine. - * If (XactMOTEngineUsed(xinfo)) - This is an optimization to avoid calling - * MOT redo commit callbacks in case of commit does not have MOT records. - * It is disabled for the time being since data used to identify storage - * engine type is cleared in 2phase commit prepare phase. - */ - CallRedoCommitCallback(xid); -#endif - /* * Mark the transaction committed in pg_clog. We use async commit * protocol during recovery to provide information on database @@ -7404,6 +7391,17 @@ static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, Transac StandbyReleaseLockTree(xid, 0, NULL); } +#ifdef ENABLE_MOT + /* + * Report committed transaction to MOT Engine. + * If (XactMOTEngineUsed(xinfo)) - This is an optimization to avoid calling + * MOT redo commit callbacks in case of commit does not have MOT records. + * It is disabled for the time being since data used to identify storage + * engine type is cleared in 2phase commit prepare phase. + */ + CallRedoCommitCallback(xid); +#endif + xact_redo_forget_alloc_segs(xid, sub_xids, nsubxacts, lsn); /* Make sure files supposed to be dropped are dropped */ diff --git a/src/gausskernel/storage/access/transam/xlog.cpp b/src/gausskernel/storage/access/transam/xlog.cpp index 3e339de18..08dea7a71 100755 --- a/src/gausskernel/storage/access/transam/xlog.cpp +++ b/src/gausskernel/storage/access/transam/xlog.cpp @@ -9702,7 +9702,9 @@ void StartupXLOG(void) /* * Recover MOT */ - MOTRecover(); + if (!IsInitdb) { + MOTRecover(); + } #endif /* initialize shared memory variables from the checkpoint record */ @@ -10879,7 +10881,9 @@ void StartupXLOG(void) /* * Cleanup MOT recovery */ - MOTRecoveryDone(); + if (!IsInitdb) { + MOTRecoveryDone(); + } #endif } diff --git a/src/gausskernel/storage/ipc/shmem.cpp b/src/gausskernel/storage/ipc/shmem.cpp index a185ef009..54a699299 100644 --- a/src/gausskernel/storage/ipc/shmem.cpp +++ b/src/gausskernel/storage/ipc/shmem.cpp @@ -126,6 +126,15 @@ void InitShmemAllocation(void) 0, sizeof(*t_thrd.xact_cxt.ShmemVariableCache)); securec_check(rc, "\0", "\0"); + +#ifdef ENABLE_MOT + /* + * Allow non backend (MOT) threads to access ShmemVariableCache for transaction manager. + */ + if (g_instance.mot_cxt.shmemVariableCache == NULL) { + g_instance.mot_cxt.shmemVariableCache = t_thrd.xact_cxt.ShmemVariableCache; + } +#endif } /* diff --git a/src/gausskernel/storage/mot/CMakeLists.txt b/src/gausskernel/storage/mot/CMakeLists.txt index 2119ecf6a..61dfe6be8 100644 --- a/src/gausskernel/storage/mot/CMakeLists.txt +++ b/src/gausskernel/storage/mot/CMakeLists.txt @@ -19,6 +19,7 @@ set(MOT_CORE_INCLUDE_PATH ${MOT_CORE_PATH}/memory/garbage_collector ${MOT_CORE_PATH}/storage ${MOT_CORE_PATH}/storage/index + ${MOT_CORE_PATH}/storage/sentinel ${MOT_CORE_PATH}/system ${MOT_CORE_PATH}/system/checkpoint ${MOT_CORE_PATH}/system/common diff --git a/src/gausskernel/storage/mot/core/Makefile.local b/src/gausskernel/storage/mot/core/Makefile.local index d69fa3750..47c314bee 100644 --- a/src/gausskernel/storage/mot/core/Makefile.local +++ b/src/gausskernel/storage/mot/core/Makefile.local @@ -100,6 +100,10 @@ else # CFLAGS += -O0 -DDEBUG CFLAGS += -DDEBUG endif +else +ifeq ($(enable_cassert), yes) + CFLAGS += -DDEBUG +endif endif CFLAGS += -faligned-new @@ -120,7 +124,6 @@ CFLAGS += -D$(MODE) CFLAGS += -fno-exceptions # disable RTTI usage CFLAGS += -fno-rtti -# TODO: enable #CFLAGS += -Woverloaded-virtual CFLAGS += -Wnon-virtual-dtor ifeq ($(ISOLATION), yes) @@ -181,7 +184,7 @@ objfiles.txt: Makefile $(OBJS) @touch $@ $(OBJ_DIR)/%.o: %.cpp - @$(CC) -c $(CFLAGS) $(CPPFLAGS) -MMD -MP -MF"$(patsubst %.o,%.d,$@)" -MT"$@" -o $@ $< + @$(CC) -c $(CFLAGS) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -MF"$(patsubst %.o,%.d,$@)" -MT"$@" -o $@ $< .PHONY: clean @@ -193,6 +196,8 @@ buildrepo: .PHONY: show show: + @echo "UNDERPG=${UNDERPG}" + @echo "enable_cassert=${enable_cassert}" @echo "CC_VERSION=${CC_VERSION}" @echo "CC=${CC}" @echo @@ -204,6 +209,8 @@ show: @echo @echo "CPPFLAGS=${CPPFLAGS}" @echo + @echo "CXXFLAGS=${CXXFLAGS}" + @echo @echo "CFLAGS=${CFLAGS}" @echo @echo "LDFLAGS=${LDFLAGS}" diff --git a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp index 80c70babf..06f6e3160 100644 --- a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp +++ b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp @@ -23,11 +23,10 @@ */ #include "occ_transaction_manager.h" -#include "../utils/utilities.h" +#include "utilities.h" #include "cycles.h" #include "mot_engine.h" #include "row.h" -#include "row_header.h" #include "txn.h" #include "txn_access.h" #include "checkpoint_manager.h" @@ -42,82 +41,151 @@ OccTransactionManager::OccTransactionManager() : m_txnCounter(0), m_abortsCounter(0), m_writeSetSize(0), - m_rowsSetSize(0), - m_deleteSetSize(0), m_insertSetSize(0), m_dynamicSleep(100), m_rowsLocked(false), m_preAbort(true), - m_validationNoWait(true) + m_validationNoWait(true), + m_isTransactionCommited(false) {} OccTransactionManager::~OccTransactionManager() {} -bool OccTransactionManager::Init() +bool OccTransactionManager::PreAbortCheck(TxnManager* txMan, GcMaintenanceInfo& gcMemoryReserve) { - bool result = true; - return result; + TxnAccess* tx = txMan->m_accessMgr; + TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); + auto itr = orderedSet.begin(); + while (itr != orderedSet.end()) { + Access* ac = (*itr).second; + switch (ac->m_type) { + case WR: + m_writeSetSize++; + gcMemoryReserve.m_version_queue++; + break; + case DEL: + m_writeSetSize++; + if (ac->m_params.IsPrimarySentinel()) { + gcMemoryReserve.m_delete_queue++; + } else { + if (ac->m_params.IsIndexUpdate()) { + gcMemoryReserve.m_update_column_queue++; + } + } + gcMemoryReserve.m_generic_queue++; + break; + case INS: + m_insertSetSize++; + m_writeSetSize++; + if (ac->m_params.IsUpgradeInsert()) { + gcMemoryReserve.m_version_queue++; + } + break; + case RD_FOR_UPDATE: + case RD: + itr = orderedSet.erase(itr); + txMan->m_accessMgr->PubReleaseAccess(ac); + continue; + default: + break; + } + + if (m_preAbort) { + if (!QuickHeaderValidation(ac)) { + if (MOTEngine::GetInstance()->IsRecovering() && ResolveRecoveryOccConflict(txMan, ac) == RC_OK) { + (void)++itr; + continue; + } + return false; + } + } + (void)++itr; + } + return true; } -bool OccTransactionManager::CheckVersion(const Access* access) +bool OccTransactionManager::QuickVersionCheck(const Access* access) { - // We always validate on committed rows! - const Row* row = access->GetRowFromHeader(); - return (row->m_rowHeader.GetCSN() == access->m_tid); + if (access->m_params.IsPrimarySentinel()) { + // For Upgrade IOD - Verify the sentinel snapshot is still valid + // Check if the key is still visible + if (access->m_snapshot <= access->m_origSentinel->GetStartCSN()) { + return false; + } + MOT_ASSERT(access->m_csn == access->m_globalRow->GetCommitSequenceNumber()); + return (access->m_csn == access->m_origSentinel->GetData()->GetCommitSequenceNumber()); + } else { + if (access->m_params.IsSecondaryUniqueSentinel()) { + PrimarySentinelNode* node = static_cast(access->m_origSentinel)->GetTopNode(); + if (node->GetEndCSN() != Sentinel::SENTINEL_INIT_CSN) { + return false; + } + return (access->m_secondaryUniqueNode == node); + } else { + MOT_ASSERT(access->GetType() == AccessType::DEL); + // Check that the Sentinel is not deleted! + return (static_cast(access->GetSentinel())->GetEndCSN() == Sentinel::SENTINEL_INIT_CSN); + } + } +} + +bool OccTransactionManager::QuickInsertCheck(const Access* access) +{ + // Lets verify the inserts + Sentinel* sent = access->m_origSentinel; + if (access->m_params.IsUpgradeInsert() == false) { + // if the sent is committed we abort! + if (sent->IsCommited()) { + return false; + } + } else { + if (access->m_params.IsPrimarySentinel()) { + // For Upgrade IOD - Verify the sentinel snapshot is still valid + if (access->m_params.IsInsertOnDeletedRow()) { + if (access->m_origSentinel->GetData()->IsRowDeleted() == false) { + return false; + } + } + return (access->m_csn == access->m_origSentinel->GetData()->GetCommitSequenceNumber()); + } else { + if (access->m_params.IsSecondaryUniqueSentinel()) { + PrimarySentinelNode* node = static_cast(access->m_origSentinel)->GetTopNode(); + if (access->m_params.IsInsertOnDeletedRow()) { + // Check if node is deleted + if (node->GetEndCSN() == Sentinel::SENTINEL_INIT_CSN) { + return false; + } + } else { + // Check if node is committed + if (node->GetEndCSN() != Sentinel::SENTINEL_INIT_CSN) { + return false; + } + } + return (access->m_secondaryUniqueNode == node); + } else { + MOT_ASSERT(access->GetType() == AccessType::INS); + MOT_ASSERT(access->m_params.IsIndexUpdate() == true); + if (static_cast(access->GetSentinel())->GetEndCSN() == + Sentinel::SENTINEL_INIT_CSN) { + return false; + } + return (static_cast(access->GetSentinel())->GetStartCSN() == access->m_csn); + } + } + } + + return true; } bool OccTransactionManager::QuickHeaderValidation(const Access* access) { if (access->m_type != INS) { // For WR/DEL/RD_FOR_UPDATE lets verify CSN - return CheckVersion(access); + return QuickVersionCheck(access); } else { - // Lets verify the inserts - // For upgrade we verify the row - // csn has not changed! - Sentinel* sent = access->m_origSentinel; - if (access->m_params.IsUpgradeInsert()) { - if (access->m_params.IsDummyDeletedRow()) { - // Check is sentinel is deleted and CSN is VALID - ABA problem - if (sent->IsCommited() == false) { - if (sent->GetData()->GetCommitSequenceNumber() != access->m_tid) { - return false; - } - } else { - return false; - } - } else { - // We deleted internally!, we only need to check version - if (sent->GetData()->GetCommitSequenceNumber() != access->m_tid) { - return false; - } - } - } else { - // If the sent is committed or inserted-deleted we abort! - if (sent->IsCommited() or sent->GetData() != nullptr) { - return false; - } - } + return QuickInsertCheck(access); } - - return true; -} - -bool OccTransactionManager::ValidateReadSet(TxnManager* txMan) -{ - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type != RD) { - continue; - } - if (!ac->GetRowFromHeader()->m_rowHeader.ValidateRead(ac->m_tid)) { - return false; - } - } - - return true; } bool OccTransactionManager::ValidateWriteSet(TxnManager* txMan) @@ -125,10 +193,6 @@ bool OccTransactionManager::ValidateWriteSet(TxnManager* txMan) TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); for (const auto& raPair : orderedSet) { const Access* ac = raPair.second; - if (ac->m_type == RD) { - continue; - } - if (!QuickHeaderValidation(ac)) { return false; } @@ -136,106 +200,65 @@ bool OccTransactionManager::ValidateWriteSet(TxnManager* txMan) return true; } -RC OccTransactionManager::LockRows(TxnManager* txMan, uint32_t& numRowsLock) +RC OccTransactionManager::LockHeaders(TxnManager* txMan, uint32_t& numSentinelsLock) { RC rc = RC_OK; - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - numRowsLock = 0; - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type == RD) { - continue; - } - if (ac->m_params.IsPrimarySentinel()) { - Row* row = ac->GetRowFromHeader(); - row->m_rowHeader.Lock(); - numRowsLock++; - MOT_ASSERT(row->GetPrimarySentinel()->IsLocked() == true); - } - } - - return rc; -} - -bool OccTransactionManager::LockHeadersNoWait(TxnManager* txMan, uint32_t& numSentinelsLock) -{ uint64_t sleepTime = 1; uint64_t thdId = txMan->GetThdId(); TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); numSentinelsLock = 0; - while (numSentinelsLock != m_writeSetSize) { - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type == RD) { - continue; + if (m_validationNoWait) { + while (numSentinelsLock != m_writeSetSize) { + for (const auto& raPair : orderedSet) { + const Access* ac = raPair.second; + Sentinel* sent = ac->m_origSentinel; + if (!sent->TryLock(thdId)) { + break; + } + numSentinelsLock++; + // New insert row is already committed! + // Check if row has changed in sentinel + if (!QuickHeaderValidation(ac)) { + rc = RC_ABORT; + goto final; + } } - Sentinel* sent = ac->m_origSentinel; - if (!sent->TryLock(thdId)) { - break; - } - numSentinelsLock++; - if (ac->m_params.IsPrimaryUpgrade()) { - ac->m_auxRow->m_rowHeader.Lock(); - } - // New insert row is already committed! - // Check if row has changed in sentinel - if (!QuickHeaderValidation(ac)) { - return false; - } - } - if (numSentinelsLock != m_writeSetSize) { - ReleaseHeaderLocks(txMan, numSentinelsLock); - numSentinelsLock = 0; - if (m_preAbort) { - for (const auto& acPair : orderedSet) { - const Access* ac = acPair.second; - if (!QuickHeaderValidation(ac)) { - return false; + if (numSentinelsLock != m_writeSetSize) { + ReleaseHeaderLocks(txMan, numSentinelsLock); + numSentinelsLock = 0; + if (m_preAbort) { + for (const auto& acPair : orderedSet) { + const Access* ac = acPair.second; + if (!QuickHeaderValidation(ac)) { + return RC_ABORT; + } } } - } - if (sleepTime > LOCK_TIME_OUT) { - return false; - } else { - if (IsHighContention() == false) { - CpuCyclesLevelTime::Sleep(5); + if (!MOTEngine::GetInstance()->IsRecovering()) { + if (sleepTime > LOCK_TIME_OUT) { + return RC_ABORT; + } else { + if (!IsHighContention()) { + CpuCyclesLevelTime::Sleep(5); + } else { + (void)usleep(m_dynamicSleep); + } + sleepTime = sleepTime << 1; + } } else { - usleep(m_dynamicSleep); + (void)usleep(1000); } - sleepTime = sleepTime << 1; } } - } - - return true; -} - -RC OccTransactionManager::LockHeaders(TxnManager* txMan, uint32_t& numSentinelsLock) -{ - RC rc = RC_OK; - uint64_t thdId = txMan->GetThdId(); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - numSentinelsLock = 0; - if (m_validationNoWait) { - if (!LockHeadersNoWait(txMan, numSentinelsLock)) { - rc = RC_ABORT; - goto final; - } } else { for (const auto& raPair : orderedSet) { const Access* ac = raPair.second; - if (ac->m_type == RD) { - continue; - } Sentinel* sent = ac->m_origSentinel; sent->Lock(thdId); numSentinelsLock++; - if (ac->m_params.IsPrimaryUpgrade()) { - ac->m_auxRow->m_rowHeader.Lock(); - } // New insert row is already committed! - // Check if row has chained in sentinel + // Check if row has changed in sentinel if (!QuickHeaderValidation(ac)) { rc = RC_ABORT; goto final; @@ -249,18 +272,15 @@ final: bool OccTransactionManager::PreAllocStableRow(TxnManager* txMan) { if (GetGlobalConfiguration().m_enableCheckpoint) { - GetCheckpointManager()->BeginCommit(txMan); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); for (const auto& raPair : orderedSet) { const Access* access = raPair.second; - if (access->m_type == RD) { + if (access->m_type == RD || (access->m_type == INS && access->m_params.IsUpgradeInsert() == false)) { continue; } if (access->m_params.IsPrimarySentinel()) { if (!GetCheckpointManager()->PreAllocStableRow(txMan, access->GetRowFromHeader(), access->m_type)) { GetCheckpointManager()->FreePreAllocStableRows(txMan); - GetCheckpointManager()->EndCommit(txMan); return false; } } @@ -269,60 +289,41 @@ bool OccTransactionManager::PreAllocStableRow(TxnManager* txMan) return true; } -bool OccTransactionManager::QuickVersionCheck(TxnManager* txMan, uint32_t& readSetSize) +bool OccTransactionManager::ReserveGcMemory(TxnManager* txMan, const GcMaintenanceInfo& gcMemoryReserve) { - int isolationLevel = txMan->GetTxnIsoLevel(); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - readSetSize = 0; - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_params.IsPrimarySentinel()) { - m_rowsSetSize++; - } - switch (ac->m_type) { - case RD_FOR_UPDATE: - case WR: - m_writeSetSize++; - break; - case DEL: - m_writeSetSize++; - m_deleteSetSize++; - break; - case INS: - m_insertSetSize++; - m_writeSetSize++; - break; - case RD: - if (isolationLevel > READ_COMMITED) { - readSetSize++; - } else { - continue; - } - break; - default: - break; - } - - if (m_preAbort) { - if (!QuickHeaderValidation(ac)) { - return false; - } - } + bool res = true; + GcManager* gc_manager = txMan->GetGcSession(); + MOT_ASSERT(gc_manager != nullptr); + res = gc_manager->ReserveGCMemoryPerQueue(GC_QUEUE_TYPE::DELETE_QUEUE, gcMemoryReserve.m_delete_queue); + if (!res) { + return false; } - return true; + res = gc_manager->ReserveGCMemoryPerQueue(GC_QUEUE_TYPE::VERSION_QUEUE, gcMemoryReserve.m_version_queue); + if (!res) { + return false; + } + res = + gc_manager->ReserveGCMemoryPerQueue(GC_QUEUE_TYPE::UPDATE_COLUMN_QUEUE, gcMemoryReserve.m_update_column_queue); + if (!res) { + return false; + } + res = gc_manager->ReserveGCMemoryPerQueue(GC_QUEUE_TYPE::GENERIC_QUEUE, gcMemoryReserve.m_generic_queue, true); + if (!res) { + return false; + } + + return res; } RC OccTransactionManager::ValidateOcc(TxnManager* txMan) { uint32_t numSentinelLock = 0; m_rowsLocked = false; - TxnAccess* tx = txMan->m_accessMgr.Get(); + TxnAccess* txnAccess = txMan->m_accessMgr; RC rc = RC_OK; - const uint32_t rowCount = tx->m_rowCnt; + const uint32_t rowCount = txnAccess->Size(); m_writeSetSize = 0; - m_rowsSetSize = 0; - m_deleteSetSize = 0; m_insertSetSize = 0; m_txnCounter++; @@ -331,42 +332,39 @@ RC OccTransactionManager::ValidateOcc(TxnManager* txMan) return rc; } - uint32_t readSetSize = 0; - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); - MOT_ASSERT(rowCount == orderedSet.size()); + GcMaintenanceInfo gcMemoryReserve{}; - /* Perform Quick Version check */ - if (!QuickVersionCheck(txMan, readSetSize)) { - rc = RC_ABORT; - goto final; - } + MOT_ASSERT(rowCount == txnAccess->GetOrderedRowSet().size()); - MOT_LOG_DEBUG("Validate OCC rowCnt=%u RD=%u WR=%u\n", tx->m_rowCnt, tx->m_rowCnt - m_writeSetSize, m_writeSetSize); - rc = LockHeaders(txMan, numSentinelLock); - if (rc != RC_OK) { - goto final; - } - - // Validate rows in the read set and write set - if (readSetSize > 0) { - if (!ValidateReadSet(txMan)) { + do { + /* 1.Perform pre-abort check and pre-processing */ + if (!PreAbortCheck(txMan, gcMemoryReserve)) { rc = RC_ABORT; - goto final; + break; } - } - if (!ValidateWriteSet(txMan)) { - rc = RC_ABORT; - goto final; - } + rc = LockHeaders(txMan, numSentinelLock); + if (rc != RC_OK) { + break; + } - // Pre-allocate stable row according to the checkpoint state. - if (!PreAllocStableRow(txMan)) { - rc = RC_MEMORY_ALLOCATION_ERROR; - goto final; - } + if (!ValidateWriteSet(txMan)) { + rc = RC_ABORT; + break; + } + + // Pre-allocate stable row according to the checkpoint state. + if (!PreAllocStableRow(txMan)) { + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + + if (!ReserveGcMemory(txMan, gcMemoryReserve)) { + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + } while (0); -final: if (likely(rc == RC_OK)) { MOT_ASSERT(numSentinelLock == m_writeSetSize); m_rowsLocked = true; @@ -380,15 +378,85 @@ final: return rc; } -void OccTransactionManager::RollbackInserts(TxnManager* txMan) +RC OccTransactionManager::ResolveRecoveryOccConflict(TxnManager* txMan, Access* access) { - return txMan->UndoInserts(); + Row* row = nullptr; + RC rc = RC_ABORT; + uint64_t endCSN = static_cast(-1); + MOT_ASSERT(access->m_type == INS); + switch (access->m_origSentinel->GetIndexOrder()) { + case IndexOrder::INDEX_ORDER_PRIMARY: + // Check what is the current row the sentinel is pointing + row = access->m_origSentinel->GetData(); + if (row) { + // Row must be deleted with Smaller CSN + if (row->IsRowDeleted() == false) { + MOT_LOG_ERROR("ERROR In Recovery Order!"); + return RC_ABORT; + } + MOT_ASSERT(access->m_origSentinel->GetData()->GetCommitSequenceNumber() > access->m_csn); + // Reset the global version and set to insert on delete + access->m_params.SetUpgradeInsert(); + access->m_params.SetInsertOnDeletedRow(); + access->m_globalRow = row; + access->m_csn = row->GetCommitSequenceNumber(); + access->m_snapshot = static_cast(-1); + rc = RC_OK; + } else { + MOT_ASSERT(false); + return RC_ABORT; + } + break; + case IndexOrder::INDEX_ORDER_SECONDARY: + endCSN = static_cast(access->m_origSentinel)->GetEndCSN(); + if (txMan->GetCommitSequenceNumber() <= endCSN) { + MOT_LOG_ERROR("ERROR In Recovery Order!"); + return RC_ABORT; + } + if (endCSN == Sentinel::SENTINEL_INIT_CSN) { + MOT_LOG_ERROR("ERROR In Recovery Order!"); + return RC_ABORT; + } + access->m_params.SetUpgradeInsert(); + access->m_params.SetInsertOnDeletedRow(); + access->m_csn = static_cast(access->m_origSentinel)->GetStartCSN(); + rc = RC_OK; + break; + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + PrimarySentinelNode* node = static_cast(access->m_origSentinel)->GetTopNode(); + if (node != nullptr) { + if (txMan->GetCommitSequenceNumber() <= node->GetEndCSN()) { + MOT_LOG_ERROR("ERROR In Recovery Order!"); + return RC_ABORT; + } + } + // Reset Visible node + access->m_params.SetUpgradeInsert(); + if (node->GetEndCSN() < Sentinel::SENTINEL_INIT_CSN) { + access->m_params.SetInsertOnDeletedRow(); + } else { + MOT_LOG_ERROR("ERROR In Recovery Order!"); + return RC_ABORT; + } + access->m_secondaryUniqueNode = node; + rc = RC_OK; + break; + } + return rc; } - -void OccTransactionManager::ApplyWrite(TxnManager* txMan) +void OccTransactionManager::WriteChanges(TxnManager* txMan) { - if (GetGlobalConfiguration().m_enableCheckpoint) { - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + if (m_writeSetSize == 0 && m_insertSetSize == 0) { + return; + } + + MOTConfiguration& cfg = GetGlobalConfiguration(); + uint64_t commit_csn = txMan->GetCommitSequenceNumber(); + uint64_t transaction_id = txMan->GetInternalTransactionId(); + TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + + // Stable rows for checkpoint needs to be created (copied from original row) before modifying the global rows. + if (cfg.m_enableCheckpoint) { for (const auto& raPair : orderedSet) { const Access* access = raPair.second; if (access->m_type == RD) { @@ -397,33 +465,40 @@ void OccTransactionManager::ApplyWrite(TxnManager* txMan) if (access->m_params.IsPrimarySentinel()) { // Pass the actual global row (access->GetRowFromHeader()), so that the stable row will have the // same CSN, rowid, etc as the original row before the modifications are applied. - GetCheckpointManager()->ApplyWrite(txMan, access->GetRowFromHeader(), access->m_type); + GetCheckpointManager()->ApplyWrite(txMan, access->GetRowFromHeader(), access); } } } -} - -void OccTransactionManager::WriteChanges(TxnManager* txMan) -{ - if (m_writeSetSize == 0 && m_insertSetSize == 0) { - return; - } - - LockRows(txMan, m_rowsSetSize); - - // Stable rows for checkpoint needs to be created (copied from original row) before modifying the global rows. - ApplyWrite(txMan); - - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // Update CSN with all relevant information on global rows // For deletes invalidate sentinels - rows still locked! for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - access->GetRowFromHeader()->m_rowHeader.WriteChangesToRow(access, txMan->GetCommitSequenceNumber()); + Access* access = raPair.second; + access->WriteGlobalChanges(commit_csn, transaction_id); } - // Treat Inserts + WriteSentinelChanges(txMan); + + // For Recovery operation:Update transactionID + for (const auto& raPair : orderedSet) { + const Access* access = raPair.second; + if (access->m_type == RD) { + continue; + } + if (access->m_params.IsPrimarySentinel()) { + static_cast(access->m_origSentinel)->SetTransactionId(transaction_id); + } + } + + m_isTransactionCommited = true; +} + +void OccTransactionManager::WriteSentinelChanges(TxnManager* txMan) +{ + uint64_t commit_csn = txMan->GetCommitSequenceNumber(); + S_SentinelNodePool* sentinelObjectPool = txMan->m_accessMgr->GetSentinelObjectPool(); + TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + if (m_insertSetSize > 0) { for (const auto& raPair : orderedSet) { Access* access = raPair.second; @@ -434,83 +509,75 @@ void OccTransactionManager::WriteChanges(TxnManager* txMan) if (access->m_params.IsUpgradeInsert() == false) { if (access->m_params.IsPrimarySentinel()) { MOT_ASSERT(access->m_origSentinel->IsDirty() == true); + MOT_ASSERT(access->m_origSentinel->IsLocked() == true); // Connect row and sentinel, row is set to absent and locked - access->m_origSentinel->SetNextPtr(access->GetRowFromHeader()); + access->m_origSentinel->SetStartCSN(commit_csn); + access->m_origSentinel->SetNextPtr(access->GetLocalInsertRow()); // Current state: row is set to absent,sentinel is locked and not dirty // Readers will not see the row + COMPILER_BARRIER; + access->m_origSentinel->SetWriteBit(); access->GetTxnRow()->GetTable()->UpdateRowCount(1); } else { - // We only set the in the secondary sentinel! - access->m_origSentinel->SetNextPtr(access->GetRowFromHeader()->GetPrimarySentinel()); + if (access->m_params.IsSecondaryUniqueSentinel() == false) { + // We only set the in the secondary sentinel! + access->m_origSentinel->SetStartCSN(commit_csn); + access->m_origSentinel->SetEndCSN(Sentinel::SENTINEL_INIT_CSN); + access->m_origSentinel->SetNextPtr(access->GetLocalInsertRow()->GetPrimarySentinel()); + } else { + access->m_origSentinel->SetStartCSN(commit_csn); + auto object = sentinelObjectPool->find(access->m_origSentinel->GetIndex()); + PrimarySentinelNode* node = object->second; + (void)sentinelObjectPool->erase(object); + node->Init( + commit_csn, Sentinel::SENTINEL_INIT_CSN, access->GetLocalInsertRow()->GetPrimarySentinel()); + access->m_origSentinel->SetNextPtr(node); + } } + // Unset Dirty - still locked! + access->m_origSentinel->UnSetDirty(); } else { - MOT_ASSERT(access->m_params.IsUniqueIndex() == true); - // Rows are locked and marked as deleted - if (access->m_params.IsPrimarySentinel()) { - /* Switch the locked row's in the sentinel - * The old row is locked and marked deleted - * The new row is locked - * Save previous row in the access! - * We need it for the row release! - */ - Row* row = access->GetRowFromHeader(); - access->m_localInsertRow = row; - access->m_origSentinel->SetNextPtr(access->m_auxRow); - // Add row to GC! - txMan->GetGcSession()->GcRecordObject(row->GetTable()->GetPrimaryIndex()->GetIndexId(), - row, - nullptr, - Row::RowDtor, - ROW_SIZE_FROM_POOL(row->GetTable())); + if (access->m_params.IsInsertOnDeletedRow()) { + if (access->m_params.IsPrimarySentinel()) { + access->GetTxnRow()->GetTable()->UpdateRowCount(1); + access->m_origSentinel->SetNextPtr(access->GetLocalInsertRow()); + COMPILER_BARRIER; + access->m_origSentinel->SetWriteBit(); + } else if (access->m_params.IsUniqueIndex() == true) { + auto object = sentinelObjectPool->find(access->m_origSentinel->GetIndex()); + PrimarySentinelNode* node = object->second; + (void)sentinelObjectPool->erase(object); + node->Init( + commit_csn, Sentinel::SENTINEL_INIT_CSN, access->GetLocalInsertRow()->GetPrimarySentinel()); + auto oldNode = static_cast(access->m_origSentinel)->GetTopNode(); + node->SetNextVersion(oldNode); + access->m_origSentinel->SetNextPtr(node); + } else { + MOT_ASSERT(access->m_params.IsIndexUpdate()); + // Revalidate End CSN + static_cast(access->m_origSentinel)->SetEndCSN(Sentinel::SENTINEL_INIT_CSN); + } } else { - // Set Sentinel for - access->m_origSentinel->SetNextPtr(access->m_auxRow->GetPrimarySentinel()); - } - // upgrade should not change the reference count! - if (access->m_origSentinel->IsCommited()) { - access->m_origSentinel->SetUpgradeCounter(); + // We delete row internally and insert a new row + if (access->m_params.IsPrimarySentinel()) { + access->m_origSentinel->SetNextPtr(access->GetLocalInsertRow()); + COMPILER_BARRIER; + access->m_origSentinel->SetWriteBit(); + } else { + auto object = sentinelObjectPool->find(access->m_origSentinel->GetIndex()); + PrimarySentinelNode* node = object->second; + (void)sentinelObjectPool->erase(object); + node->Init( + commit_csn, Sentinel::SENTINEL_INIT_CSN, access->GetLocalInsertRow()->GetPrimarySentinel()); + auto oldNode = static_cast(access->m_origSentinel)->GetTopNode(); + oldNode->SetEndCSN(commit_csn); + node->SetNextVersion(oldNode); + access->m_origSentinel->SetNextPtr(node); + } } } } } - - // Treat Inserts - if (m_insertSetSize > 0) { - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type != INS) { - continue; - } - access->m_origSentinel->UnSetDirty(); - } - } - - CleanRowsFromIndexes(txMan); -} - -void OccTransactionManager::CleanRowsFromIndexes(TxnManager* txMan) -{ - if (m_deleteSetSize == 0) { - return; - } - - TxnAccess* tx = txMan->m_accessMgr.Get(); - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); - uint32_t numOfDeletes = m_deleteSetSize; - // use local counter to optimize - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == DEL) { - numOfDeletes--; - access->GetTxnRow()->GetTable()->UpdateRowCount(-1); - MOT_ASSERT(access->m_params.IsUpgradeInsert() == false); - // Use Txn Row as row may change INSERT after DELETE leaves residue - txMan->RemoveKeyFromIndex(access->GetTxnRow(), access->m_origSentinel); - } - if (!numOfDeletes) { - break; - } - } } void OccTransactionManager::ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOfLocks) @@ -519,7 +586,7 @@ void OccTransactionManager::ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOf return; } - TxnAccess* tx = txMan->m_accessMgr.Get(); + TxnAccess* tx = txMan->m_accessMgr; TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); // use local counter to optimize for (const auto& raPair : orderedSet) { @@ -528,41 +595,7 @@ void OccTransactionManager::ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOf continue; } else { numOfLocks--; - access->m_origSentinel->Release(); - if (access->m_params.IsPrimaryUpgrade()) { - access->m_auxRow->m_rowHeader.Release(); - } - } - if (!numOfLocks) { - break; - } - } -} - -void OccTransactionManager::ReleaseRowsLocks(TxnManager* txMan, uint32_t numOfLocks) -{ - if (numOfLocks == 0) { - return; - } - - TxnAccess* tx = txMan->m_accessMgr.Get(); - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); - - // use local counter to optimize - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == RD) { - continue; - } - - if (access->m_params.IsPrimarySentinel()) { - numOfLocks--; - access->GetRowFromHeader()->m_rowHeader.Release(); - if (access->m_params.IsUpgradeInsert()) { - // This is the global row that we switched! - // Currently it's in the gc! - access->m_localInsertRow->m_rowHeader.Release(); - } + access->m_origSentinel->Unlock(); } if (!numOfLocks) { break; @@ -574,6 +607,6 @@ void OccTransactionManager::CleanUp() { m_writeSetSize = 0; m_insertSetSize = 0; - m_rowsSetSize = 0; + m_isTransactionCommited = false; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.h b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.h index 721dc8d38..b15da68c7 100644 --- a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.h +++ b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.h @@ -33,6 +33,14 @@ class Access; class TxnManager; constexpr uint64_t LOCK_TIME_OUT = 1 << 16; + +struct GcMaintenanceInfo { + GcMaintenanceInfo() = default; + uint32_t m_version_queue = 0; + uint32_t m_delete_queue = 0; + uint32_t m_update_column_queue = 0; + uint32_t m_generic_queue = 0; +}; /** * @class OccTransactionManager * @brief Optimistic concurrency control implementation. @@ -45,11 +53,6 @@ public: /** @brief Destructor. */ ~OccTransactionManager(); - /** - * @brief Initialize _writeSet, _readSet and _insertSet array. - */ - bool Init(); - /** * @brief Sets or clears the pre-abort flag. * @detail Determines whether to call quickVersionCheck() during @@ -81,10 +84,6 @@ public: */ RC ValidateOcc(TxnManager* tx); - RC LockHeaders(TxnManager* txMan, uint32_t& numSentinelsLock); - - RC LockRows(TxnManager* txMan, uint32_t& numRowsLock); - /** * @brief Writes all the changes in the write set of a transaction and * release the locks associated with all the write access items. @@ -92,17 +91,17 @@ public: */ void WriteChanges(TxnManager* txMan); - /** @brief remove all deleted keys from the global indices */ - void CleanRowsFromIndexes(TxnManager* txMan); - - /** @brief Rollack insert-set due to an abort */ - void RollbackInserts(TxnManager* txMan); + /** + * @brief Writes all the changes in the insert set of a transaction + * and release the locks associated with all the write access items. + * @param txMan The committing transaction. + */ + void WriteSentinelChanges(TxnManager* txMan); void ReleaseLocks(TxnManager* txMan) { if (m_rowsLocked) { ReleaseHeaderLocks(txMan, m_writeSetSize); - ReleaseRowsLocks(txMan, m_rowsSetSize); m_rowsLocked = false; } } @@ -139,57 +138,56 @@ public: } } -private: - /** - * @brief Checks whether the transaction identifier in the original row - * in the access matches the transaction identifier in the access object. - * @detail The check is faster than a cc validate, but it does not - * guarantee correctness. If the versions do not match, cc - * verification will always fail too. However, if the function returns - * true, cc may fail during the verification (i.e. it may produce - * false positive reports). - */ - bool CheckVersion(const Access* access); + inline bool IsTransactionCommited() const + { + return m_isTransactionCommited; + } - /** @brief Validate Header for insert */ + inline uint64_t GetTxnCounter() const + { + return m_txnCounter; + } + +private: + /** @brief Perform validation of current access */ + bool QuickVersionCheck(const Access* access); + + /** @brief Perform validation of current access */ + bool QuickInsertCheck(const Access* access); + + /** @brief Perform pre-processing and validation */ + bool PreAbortCheck(TxnManager* txMan, GcMaintenanceInfo& gcMemoryReserve); + + /** @brief Validate Header for insert */ bool QuickHeaderValidation(const Access* access); - bool QuickVersionCheck(TxnManager* txMan, uint32_t& readSetSize); - - bool LockHeadersNoWait(TxnManager* txMan, uint32_t& numSentinelsLock); + /** @brief Lock all keys */ + RC LockHeaders(TxnManager* txMan, uint32_t& numSentinelsLock); + /** @brief Release Header locks */ void ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOfLocks); - /** @brief Release all the locked rows */ - void ReleaseRowsLocks(TxnManager* txMan, uint32_t numOfLocks); + /** For Recovery, takes care of IDI (Insert, Delete, Insert) use case */ + RC ResolveRecoveryOccConflict(TxnManager* txMan, Access* access); - /** @brief Validate the read set */ - bool ValidateReadSet(TxnManager* txMan); - - /** @brief Validate the write set */ + /** @brief validate the write set */ bool ValidateWriteSet(TxnManager* txMan); /** @brief Pre-allocates stable row according to the checkpoint state. */ bool PreAllocStableRow(TxnManager* txMan); - /** @brief Sets stable row according to the checkpoint state. */ - void ApplyWrite(TxnManager* txMan); + /** @brief Pre-allocates GC memory for reclamation. */ + bool ReserveGcMemory(TxnManager* txMan, const GcMaintenanceInfo& gcMemoryReserve); /** @var transaction counter */ - uint32_t m_txnCounter; + uint64_t m_txnCounter; /** @var aborts counter */ - uint32_t m_abortsCounter; + uint64_t m_abortsCounter; /** @var Write set size. */ uint32_t m_writeSetSize; - /** @var total number of rows */ - uint32_t m_rowsSetSize; - - /** @var Write set size. */ - uint32_t m_deleteSetSize; - /** @var Write set size. */ uint32_t m_insertSetSize; @@ -203,6 +201,9 @@ private: /** @var Validate-no-wait configuration. */ bool m_validationNoWait; + + /** @var flag indicating whether transaction committed */ + bool m_isTransactionCommited; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp b/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp deleted file mode 100644 index ee10b1664..000000000 --- a/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * row_header.cpp - * Row header implementation in OCC - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp - * - * ------------------------------------------------------------------------- - */ - -#include "row_header.h" -#include "global.h" -#include "mot_atomic_ops.h" -#include "row.h" -#include "txn_access.h" -#include "utilities.h" -#include "cycles.h" -#include "debug_utils.h" -#include "mot_engine.h" - -namespace MOT { -DECLARE_LOGGER(RowHeader, ConcurrenyControl); - -RC RowHeader::GetLocalCopy( - TxnAccess* txn, AccessType type, Row* localRow, const Row* origRow, TransactionId& lastTid) const -{ - uint64_t sleepTime = 1; - uint64_t v = 0; - uint64_t v2 = 1; - - // concurrent update/delete after delete is not allowed - abort current transaction - if ((m_csnWord & ABSENT_BIT) && type != AccessType::INS) { - return RC_ABORT; - } - - while (v2 != v) { - // contend for exclusive access - v = m_csnWord; - while (v & LOCK_BIT) { - if (sleepTime > LOCK_TIME_OUT) { - sleepTime = LOCK_TIME_OUT; - struct timespec ts = {0, 5000}; - (void)nanosleep(&ts, NULL); - } else { - CpuCyclesLevelTime::Sleep(1); - sleepTime = sleepTime << 1; - } - - v = m_csnWord; - } - // No need to copy new-row. - if (type != AccessType::INS) { // get current row contents (not required during insertion of new row) - localRow->Copy(origRow); - } - COMPILER_BARRIER - v2 = m_csnWord; - } - if ((v & ABSENT_BIT) && (v & LATEST_VER_BIT)) { - return RC_ABORT; - } - lastTid = v & (~LOCK_BIT); - - if (type == AccessType::INS) { - // ROW ALREADY COMMITED - if ((m_csnWord & (ABSENT_BIT)) == 0) { - return RC_ABORT; - } - lastTid &= (~ABSENT_BIT); - } - - return RC_OK; -} - -bool RowHeader::ValidateWrite(TransactionId tid) const -{ - return (tid == GetCSN()); -} - -bool RowHeader::ValidateRead(TransactionId tid) const -{ - if (IsLocked() or (tid != GetCSN())) { - return false; - } - - return true; -} - -void RowHeader::WriteChangesToRow(const Access* access, uint64_t csn) -{ - Row* row = access->GetRowFromHeader(); - AccessType type = access->m_type; - - if (type == RD) { - return; - } -#ifdef MOT_DEBUG - if (access->m_params.IsPrimarySentinel()) { - uint64_t v = m_csnWord; - if (!MOTEngine::GetInstance()->IsRecovering()) { - if (!(csn > GetCSN() && (v & LOCK_BIT))) { - MOT_LOG_ERROR( - "csn=%ld, v & LOCK_BIT=%ld, v & (~LOCK_BIT)=%ld\n", csn, (v & LOCK_BIT), (v & (~LOCK_BIT))); - MOT_ASSERT(false); - } - } - } -#endif - switch (type) { - case WR: - MOT_ASSERT(access->m_params.IsPrimarySentinel() == true); - row->Copy(access->m_localRow); - m_csnWord = (csn | LOCK_BIT); - break; - case DEL: - MOT_ASSERT(access->m_origSentinel->IsCommited() == true); - if (access->m_params.IsPrimarySentinel()) { - m_csnWord = (csn | LOCK_BIT | ABSENT_BIT | LATEST_VER_BIT); - // and allow reuse of the original row - } - // Invalidate sentinel - row is still locked! - access->m_origSentinel->SetDirty(); - break; - case INS: - if (access->m_params.IsPrimarySentinel()) { - // At this case we have the new-row and the old row - if (access->m_params.IsUpgradeInsert()) { - // We set the global-row to be locked and deleted - m_csnWord = (csn | LOCK_BIT | LATEST_VER_BIT); - // The new row is locked and absent! - access->m_auxRow->UnsetAbsentRow(); - access->m_auxRow->SetCommitSequenceNumber(csn); - } else { - m_csnWord = (csn | LOCK_BIT); - } - } - break; - default: - break; - } -} - -void RowHeader::Lock() -{ - uint64_t v = m_csnWord; - while ((v & LOCK_BIT) || !__sync_bool_compare_and_swap(&m_csnWord, v, v | LOCK_BIT)) { - PAUSE - v = m_csnWord; - } -} -} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/concurrency_control/row_header.h b/src/gausskernel/storage/mot/core/concurrency_control/row_header.h deleted file mode 100644 index b54b4e025..000000000 --- a/src/gausskernel/storage/mot/core/concurrency_control/row_header.h +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * row_header.h - * Row header implementation in OCC - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/concurrency_control/row_header.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef ROW_HEADER_H -#define ROW_HEADER_H - -#include -#include "global.h" -#include "mot_atomic_ops.h" -#include "debug_utils.h" - -namespace MOT { -// forward declarations -class Catalog; -class Index; -class Row; -class TxnManager; -class TxnAccess; -class CheckpointWorkerPool; -class RecoveryOps; -// forward declarations -enum RC : uint32_t; -enum AccessType : uint8_t; - -// forward declaration -class OccTransactionManager; -class Access; - -/** @define masks for CSN word */ -#define CSN_BITS 0x1FFFFFFFFFFFFFFFUL - -#define STATUS_BITS 0xE000000000000000UL - -/** @define Bit position designating locked state. */ -#define LOCK_BIT (1UL << 63) - -/** @define Bit position designation version. */ -#define LATEST_VER_BIT (1UL << 62) - -/** @define Bit position designating absent row. */ -#define ABSENT_BIT (1UL << 61) - -/** - * @class RowHeader - * @brief Helper class for managing a single row in Optimistic concurrency control. - */ -class __attribute__((__packed__)) RowHeader { -public: - /** - * @brief Constructor. - * @param row The row manage. - */ - explicit inline RowHeader() : m_csnWord(0) - {} - - inline RowHeader(const RowHeader& src) - { - m_csnWord = src.m_csnWord; - } - - // class non-copy-able, non-assignable, non-movable - /** @cond EXCLUDE_DOC */ - RowHeader(RowHeader&&) = delete; - - RowHeader& operator=(const RowHeader&) = delete; - - RowHeader& operator=(RowHeader&&) = delete; - /** @endcond */ - -private: - /** - * @brief Gets a consistent snapshot of a managed row. - * @param txn The transaction access object. - * @param type The access type. - * @param[out] localRow Receives the row contents except during new row - * insertion. - * @return Return code denoting success or failure. - */ - RC GetLocalCopy(TxnAccess* txn, AccessType type, Row* localRow, const Row* origRow, TransactionId& lastTid) const; - - /** - * @brief Validates the row was not changed by a concurrent transaction - * @param tid The transaction identifier. - * @return Boolean value denoting whether the row header is valid. - */ - bool ValidateWrite(TransactionId tid) const; - - /** - * @brief Validates the row was not changed or locked by a concurrent transaction - * @param tid The transaction identifier. - * @return Boolean value denoting whether the row header is valid. - */ - bool ValidateRead(TransactionId tid) const; - - /** - * @brief Apply changes to the public row - * @param access Container for the row - * @param csn Commit Serial Number - */ - void WriteChangesToRow(const Access* access, uint64_t csn); - - /** @brief Locks the row. */ - void Lock(); - - /** @brief Unlocks the row. */ - void Release() - { - MOT_ASSERT(m_csnWord & LOCK_BIT); -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) - m_csnWord = m_csnWord & (~LOCK_BIT); -#else - uint64_t v = m_csnWord; - while (!__sync_bool_compare_and_swap(&m_csnWord, v, (v & ~LOCK_BIT))) { - PAUSE - v = m_csnWord; - } -#endif - } - - /** - * @brief Attempts to lock the row. - * @return True if the row was locked. - */ - bool TryLock() - { - uint64_t v = m_csnWord; - if (v & LOCK_BIT) { // already locked - return false; - } - return __sync_bool_compare_and_swap(&m_csnWord, v, (v | LOCK_BIT)); - } - - /** @var Assert row is locked. */ - void AssertLock() const - { - MOT_ASSERT(m_csnWord & LOCK_BIT); - } - - /** - * @brief Retrieves the transaction identifier stripped off all - * meta-data. - * @return The bare transaction identifier. - */ - uint64_t GetCSN() const - { - return (m_csnWord & CSN_BITS); - } - - /** Set the CSN of the row */ - void SetCSN(uint64_t csn) - { - m_csnWord = (m_csnWord & STATUS_BITS) | (csn & CSN_BITS); - } - - /** - * @brief Queries whether the absent bit is set for the row. - * @return True if the absent bit is set in the row. - */ - bool IsAbsent() const - { - return (m_csnWord & ABSENT_BIT) == ABSENT_BIT; - } - - /** - * @brief Queries whether the lock bit is set in the row. - * @return True if the lock bit is set in the row. - */ - bool IsLocked() const - { - return (m_csnWord & LOCK_BIT) == LOCK_BIT; - } - - /** - * @brief Queries whether the row is deleted. - * @return True if the absent bit and the latet vsersion are set for the row. - */ - bool IsRowDeleted() const - { - return (m_csnWord & (ABSENT_BIT | LATEST_VER_BIT)) == (ABSENT_BIT | LATEST_VER_BIT); - } - - /** @brief Sets the absent bit in the row. */ - void SetAbsentBit() - { - m_csnWord |= ABSENT_BIT; - } - - /** @brief Sets the absent and locked bits in the row. */ - void SetAbsentLockedBit() - { - m_csnWord |= (ABSENT_BIT | LOCK_BIT); - } - - /** @brief Set the deletion bits in the row */ - void SetDeleted() - { - m_csnWord |= (ABSENT_BIT | LATEST_VER_BIT); - } - - /** @brief Clears the absent bit in the row. */ - void UnsetAbsentBit() - { - m_csnWord &= ~ABSENT_BIT; - } - - /** @brief Usnet latest version bit */ - void UnsetLatestVersionBit() - { - m_csnWord &= ~LATEST_VER_BIT; - } - -private: - /** @var Transaction identifier and meta data for locking and deleting a row. */ - volatile uint64_t m_csnWord; - - // Allows privileged access - friend Row; - friend OccTransactionManager; - friend CheckpointWorkerPool; - friend RecoveryOps; - friend Index; -}; -} // namespace MOT - -#endif /* ROW_HEADER_H */ diff --git a/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.h b/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.h index 852a6ecaf..7e9305956 100644 --- a/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.h +++ b/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.h @@ -41,12 +41,17 @@ public: * @param argc Number of command-line arguments. */ CmdLineConfigLoader(char** argv, int argc) - : ConfigLoader("CmdLine", CMDLINE_CONFIG_PRIORITY), m_cmdlineConfigTree(ParseCmdLine(argv, argc)) + : ConfigLoader("CmdLine", CMDLINE_CONFIG_PRIORITY), m_cmdlineConfigTree(ParseCmdLine(argv, argc)), m_owner(true) {} /** @brief Destructor. */ ~CmdLineConfigLoader() override - {} + { + if (m_owner) { + delete m_cmdlineConfigTree; + } + m_cmdlineConfigTree = nullptr; + } protected: /** @@ -55,6 +60,7 @@ protected: */ ConfigTree* LoadConfig() override { + m_owner = false; return m_cmdlineConfigTree; } @@ -62,6 +68,9 @@ private: /** @var Cached configuration, loaded only once and never modified. */ ConfigTree* m_cmdlineConfigTree; + /** @var Specifies if m_cmdlineConfigTree should be freed or not. */ + bool m_owner = false; + /** * @brief Parses the command line arguments. * @param argv Command-line argument string array. diff --git a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp index 83171dacc..3b27ca5dd 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp @@ -76,11 +76,17 @@ bool ConfigFileLoader::HasChanged() mot_string ConfigFileLoader::ComposeFullName(const char* typeName, const char* name, const char* configFilePath) { mot_string result; - result.format("%s[%s]@%s", typeName, name, configFilePath); + if (!result.format("%s[%s]@%s", typeName, name, configFilePath)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Load Configuration", + "Failed to format configuration file loader name: %s", + configFilePath); + // this is not a critical error, we continue with ill-formatted configuration loader name + } return result; } -uint64_t ConfigFileLoader::GetFileModificationTime() +uint64_t ConfigFileLoader::GetFileModificationTime() const { uint64_t filetime = 0; struct stat buf; diff --git a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.h b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.h index 8f6cc48b7..58c12972a 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.h +++ b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.h @@ -102,7 +102,7 @@ private: * @brief Retrieves the last modification time of the configuration file. * @return The last modification time of the configuration file or zero if failed. */ - uint64_t GetFileModificationTime(); + uint64_t GetFileModificationTime() const; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp b/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp index 7c58642b7..b4e8f2e5d 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp @@ -28,7 +28,7 @@ #include "mot_error.h" #include -#include +#include #include #include #include @@ -69,32 +69,51 @@ bool ConfigFileParser::ParseKeyValue(const mot_string& keyValuePart, const mot_s "Load Configuration", "Failed to parse key/value: missing equal sign (%s)", keyValuePart.c_str()); - } else if (!keyValuePart.substr(key, 0, equalPos) || !keyValuePart.substr(value, equalPos + 1)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse key/value"); - } else { - key.trim(); - value.trim(); - result = true; + return result; + } - // check for array item - uint32_t poundPos = key.find('#'); - if (poundPos != mot_string::npos) { - result = false; - mot_string intPart; - if (!key.substr(intPart, poundPos + 1)) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse integer part from array item specifier"); + if (!keyValuePart.substr(key, 0, equalPos) || !keyValuePart.substr(value, equalPos + 1)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse key/value"); + return result; + } + + key.trim(); + value.trim(); + + uint32_t startQuotePos = value.find('\''); + if (startQuotePos == 0) { + // Value part starts with '\'', probably enclosed in single quotes. + uint32_t endQuotePos = value.find_last_of('\''); + if (startQuotePos == endQuotePos || endQuotePos != (value.length() - 1)) { + // Value part starts with '\'', but not enclosed in single quotes properly, illegal format. + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse value"); + return result; + } + uint32_t valueLength = ((endQuotePos - startQuotePos) - 1); + value.substr_inplace(startQuotePos + 1, valueLength); + value.trim(); + } + + result = true; + + // check for array item + uint32_t poundPos = key.find('#'); + if (poundPos != mot_string::npos) { + result = false; + mot_string intPart; + if (!key.substr(intPart, poundPos + 1)) { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse integer part from array item specifier"); + } else { + if (ParseUIntValue(section, key, intPart, arrayIndex, true)) { + hasArrayIndex = true; + key.substr_inplace(0, poundPos); + result = true; } else { - if (ParseUIntValue(section, key, intPart, arrayIndex, true)) { - hasArrayIndex = true; - key.substr_inplace(0, poundPos); - result = true; - } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, - "Load Configuration", - "Invalid array item specifier encountered: %s", - key.c_str()); - } + MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, + "Load Configuration", + "Invalid array item specifier encountered: %s", + key.c_str()); } } } diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item.cpp b/src/gausskernel/storage/mot/core/infra/config/config_item.cpp index 3d8ed98fe..2a5486207 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_item.cpp @@ -80,7 +80,7 @@ bool ConfigItem::Initialize(const char* path, const char* name) return result; } -uint32_t ConfigItem::ComputeDepth() +uint32_t ConfigItem::ComputeDepth() const { uint32_t result = 0; if (m_fullPathName.compare(PATH_SEP_STR) != 0) { diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item.h b/src/gausskernel/storage/mot/core/infra/config/config_item.h index 9b427dc7c..eeabef8c2 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item.h +++ b/src/gausskernel/storage/mot/core/infra/config/config_item.h @@ -177,7 +177,7 @@ private: uint32_t m_depth; /** @brief Compute section depth. */ - uint32_t ComputeDepth(); + uint32_t ComputeDepth() const; }; /** @typedef The data structure for holding a list of configuration items. */ diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp b/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp index f9d07b4ec..3dc941dd9 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp @@ -23,7 +23,7 @@ */ #include "config_item_class.h" -#include +#include namespace MOT { static const char* CONFIG_ITEM_SECTION_STR = "section"; diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item_class.h b/src/gausskernel/storage/mot/core/infra/config/config_item_class.h index eeaa929d3..60194bc2b 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item_class.h +++ b/src/gausskernel/storage/mot/core/infra/config/config_item_class.h @@ -25,7 +25,7 @@ #ifndef CONFIG_ITEM_CLASS_H #define CONFIG_ITEM_CLASS_H -#include +#include namespace MOT { /** diff --git a/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp index b8d1bddc5..ca3159799 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp @@ -25,7 +25,7 @@ #include "global.h" #include "config_loader.h" -#include +#include namespace MOT { diff --git a/src/gausskernel/storage/mot/core/infra/config/config_loader.h b/src/gausskernel/storage/mot/core/infra/config/config_loader.h index f7bcda250..4ed3f3f2e 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_loader.h +++ b/src/gausskernel/storage/mot/core/infra/config/config_loader.h @@ -50,16 +50,6 @@ namespace MOT { * @brief The base interface for all configuration loaders. */ class ConfigLoader { -protected: - /** - * @brief Constructor. - * @param name The name of the configuration loader. Must be unique among all loaders. - * @param priority The priority of the configuration loader. Lower numbers mean higher priority. - * This is for managing layered configuration and it determines precedence while merging - * configuration trees. - */ - ConfigLoader(const char* name, uint32_t priority); - public: /** Destructor. */ virtual ~ConfigLoader() @@ -140,6 +130,15 @@ public: } protected: + /** + * @brief Constructor. + * @param name The name of the configuration loader. Must be unique among all loaders. + * @param priority The priority of the configuration loader. Lower numbers mean higher priority. + * This is for managing layered configuration and it determines precedence while merging + * configuration trees. + */ + ConfigLoader(const char* name, uint32_t priority); + /** * @brief Implement configuration loading. * @return The resulting configuration tree or null pointer if failed. diff --git a/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp b/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp index e4c05c052..6237826fe 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp @@ -31,7 +31,7 @@ #include "log_level_formatter.h" #include -#include +#include #include namespace MOT { @@ -258,17 +258,18 @@ bool ConfigManager::InitLoad() MOT_LOG_ERROR_STACK("Failed to load configuration (MOT engine continues to load):"); } ClearErrorStack(); // reset error state - result = true; // nevertheless we continue with default configuration - } else { - // trigger update from just-loaded configuration (only to this specific listener). - MOT_LOG_DEBUG("Reloaded MOTConfiguration from main configuration"); - MOTConfiguration& motCfg = GetGlobalConfiguration(); - motCfg.OnConfigChange(); - // now the main configuration is fully loaded, so let's validate it - if (motCfg.IsValid()) { - result = true; - } + // nevertheless we continue with default configuration + } + + // trigger update from just-loaded configuration (only to this specific listener). + MOT_LOG_DEBUG("Reloaded MOTConfiguration from main configuration"); + MOTConfiguration& motCfg = GetGlobalConfiguration(); + motCfg.OnConfigChange(); + + // now the main configuration is fully loaded, so let's validate it + if (motCfg.IsValid()) { + result = true; } return result; } @@ -291,7 +292,7 @@ bool ConfigManager::Initialize(char** argv, int argc) char* envvar = getenv("MOT_DEBUG_CFG_LOAD"); if (envvar && (strcmp(envvar, "TRUE") == 0)) { - SetLogComponentLogLevel("Configuration", LogLevel::LL_DEBUG); + (void)SetLogComponentLogLevel("Configuration", LogLevel::LL_DEBUG); } // add command line configuration loader @@ -366,15 +367,22 @@ bool ConfigManager::ReloadConfig(bool ignoreErrors /* = true */) for (uint32_t i = 0; i < m_configLoaderCount; ++i) { ConfigLoader* configLoader = m_configLoaders[i]; ConfigTree* configTree = configLoader->Load(); - if (configTree != nullptr) { - MOT_LOG_DEBUG("Adding configuration tree %p with priority %u from configuration loader %s", - configTree, - configTree->GetPriority(), - configLoader->GetName()); - m_layeredConfigTree.AddConfigTree(configTree); - } else { + if (configTree == nullptr) { MOT_LOG_WARN("Failed to load configuration tree from configuration loader %s, configuration from this " - "loader will be ignored. Please fix configuration and trigger reload.", + "loader will be ignored. Please fix configuration and reload.", + configLoader->GetName()); + if (!ignoreErrors) { + result = false; + } + continue; + } + MOT_LOG_DEBUG("Adding configuration tree %p with priority %u from configuration loader %s", + configTree, + configTree->GetPriority(), + configLoader->GetName()); + if (!m_layeredConfigTree.AddConfigTree(configTree)) { + MOT_LOG_WARN("Failed to add configuration tree from configuration loader %s, configuration from this " + "loader will be ignored. Please fix configuration and reload.", configLoader->GetName()); if (!ignoreErrors) { result = false; @@ -384,7 +392,7 @@ bool ConfigManager::ReloadConfig(bool ignoreErrors /* = true */) // print loaded configuration LogLevel logLevel = LogLevel::LL_TRACE; - GetLayeredConfigTree()->GetUserConfigValue("Log.cfg_startup_log_level", logLevel, false); + logLevel = GetLayeredConfigTree()->GetUserConfigValue("Log.cfg_startup_log_level", logLevel, false); if (MOT_CHECK_LOG_LEVEL(logLevel)) { GetLayeredConfigTree()->Print(logLevel); } diff --git a/src/gausskernel/storage/mot/core/infra/config/config_section.cpp b/src/gausskernel/storage/mot/core/infra/config/config_section.cpp index be4a3a2fc..bf4a356fa 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_section.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_section.cpp @@ -104,7 +104,6 @@ bool ConfigSection::GetConfigSectionNames(mot_string_list& sectionNames) const bool ConfigSection::GetConfigValueNames(mot_string_list& valueNames) const { - bool result = true; ConfigValueMap::const_iterator itr = m_valueMap.begin(); while (itr != m_valueMap.end()) { if (!valueNames.push_back(itr->first)) { @@ -366,7 +365,7 @@ bool ConfigSection::MergeValues(ConfigSection* configSection) ConfigValue* oldConfigValue = itr2->second; MOT_LOG_DEBUG(" *** --> Deleting merged section value %s", oldConfigValue->GetFullPathName()); oldConfigValue->Print(LogLevel::LL_DEBUG, true); - m_valueMap.erase(itr2); + (void)m_valueMap.erase(itr2); MOT_LOG_DEBUG(" *** --> Delete merged section value done"); delete oldConfigValue; if (!m_valueMap.insert(ConfigValueMap::value_type(configValue->GetName(), configValue)).second) { @@ -415,7 +414,7 @@ bool ConfigSection::MergeArrays(ConfigSection* configSection) // not inserted, so replace existing value ConfigArrayMap::iterator& itr2 = pairis.first; delete itr2->second; - m_arrayMap.erase(itr2); + (void)m_arrayMap.erase(itr2); if (!m_arrayMap.insert(ConfigArrayMap::value_type(configArray->GetName(), configArray)).second) { if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, @@ -434,7 +433,7 @@ bool ConfigSection::MergeArrays(ConfigSection* configSection) return false; } } - itr++; + (void)itr++; } configSection->m_arrayMap.clear(); // required to make sure items are not deleted in destructor return true; diff --git a/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp b/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp index e6417f8ca..93eed4800 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp @@ -23,7 +23,7 @@ */ #include "config_value_type.h" -#include "string.h" +#include namespace MOT { static const char* CONFIG_VALUE_INT64_STR = "int64"; diff --git a/src/gausskernel/storage/mot/core/infra/config/config_value_type.h b/src/gausskernel/storage/mot/core/infra/config/config_value_type.h index 46ccb64dc..c354e936f 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_value_type.h +++ b/src/gausskernel/storage/mot/core/infra/config/config_value_type.h @@ -25,7 +25,7 @@ #ifndef CONFIG_VALUE_TYPE_H #define CONFIG_VALUE_TYPE_H -#include +#include namespace MOT { /** diff --git a/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.h b/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.h index edf5f8eea..6363aad8d 100644 --- a/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.h +++ b/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.h @@ -100,12 +100,11 @@ protected: /** * @brief Loads external configuration. - * @detail The envelope should override and implement this method to actually build the - * configuration tree for this loader. If the purpose is to override configuration values loaded - * by other loaders, then make sure to build a configuration tree with identical paths. - * @note While overriding this method, the envelope can call the utility helper functions devised - * for this purpose as follows: - * + * @detail The envelope should override and implement this method to actually build the configuration tree for this + * loader. If the purpose is to override configuration values loaded by other loaders, then make sure to build a + * configuration tree with identical paths. + * @note While overriding this method, the envelope can call the utility helper functions devised for this purpose + * as follows: * * ... * if (!AddExtStringConfigItem(...)) { @@ -116,7 +115,6 @@ protected: * } * ... * - * * @ref AddConfigItem(). */ virtual bool OnLoadExtConfig() @@ -238,6 +236,8 @@ protected: return AddExtStringConfigItem(path, key, TypeFormatter::ToString(value, str)); } + DECLARE_CLASS_LOGGER() + private: /** @var The section map used to build the configuration tree. */ ConfigSectionMap m_sectionMap; @@ -275,7 +275,7 @@ private: bool EndExtConfigLoad(); /** Free all resources associated with the external loader. */ - void Cleanup(); + void Cleanup() noexcept; /** * @brief Helper function for adding a typed configuration item. @@ -285,7 +285,7 @@ private: * @return True if succeeded, otherwise false. */ template > - inline bool AddTypedConfigItem(const char* path, const char* key, T value) + inline bool AddTypedConfigItem(const char* path, const char* key, const T& value) { bool result = false; @@ -305,9 +305,6 @@ private: return result; } - -protected: - DECLARE_CLASS_LOGGER() }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp index a6325f19f..66aff757d 100644 --- a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp @@ -26,7 +26,11 @@ namespace MOT { FileLineReader::FileLineReader(const char* configFilePath) - : m_configFilePath(configFilePath), m_configFile(configFilePath), m_lineItr(NULL), m_lineNumber(0) + : m_configFilePath(configFilePath), + m_configFile(configFilePath), + m_lineItr(NULL), + m_lineNumber(0), + m_initialized(false) { ParseFile(); } @@ -36,14 +40,18 @@ FileLineReader::~FileLineReader() void FileLineReader::ParseFile() { - if (m_configFile) { + if (m_configFile.is_open()) { std::string line; while (std::getline(m_configFile, line)) { - m_lines.push_back(line.c_str()); + if (!m_lines.push_back(line.c_str())) { + m_initialized = false; + return; + } } m_lineItr = m_lines.cbegin(); m_lineNumber = 1; + m_initialized = true; } else { m_lineItr = m_lines.cend(); } diff --git a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.h b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.h index 736500c4a..494d3146a 100644 --- a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.h +++ b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.h @@ -53,6 +53,15 @@ public: return m_configFile.is_open(); } + /** + * @brief Queries whether the configuration file was fully loaded. + * @return True if all data were read from file. + */ + inline bool IsInitialized() const + { + return m_initialized; + } + /** * @brief Retrieves the configuration file path. * @return The configuration file path. @@ -116,6 +125,9 @@ private: /** @var The currently parsed line number. */ uint32_t m_lineNumber; + /** @var Initialization state. */ + bool m_initialized; + /** @brief Parses the configuration file, breaking it into lines. */ void ParseFile(); }; diff --git a/src/gausskernel/storage/mot/core/infra/config/iconfig_change_listener.h b/src/gausskernel/storage/mot/core/infra/config/iconfig_change_listener.h index 6e0e752f5..3739a18c3 100644 --- a/src/gausskernel/storage/mot/core/infra/config/iconfig_change_listener.h +++ b/src/gausskernel/storage/mot/core/infra/config/iconfig_change_listener.h @@ -31,21 +31,21 @@ namespace MOT { * @brief Configuration change listener interface. */ class IConfigChangeListener { -protected: - /** Constructor. */ - IConfigChangeListener() - {} - public: - /** Destructor. */ - virtual ~IConfigChangeListener() - {} - /** * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ virtual void OnConfigChange() = 0; + +protected: + /** Constructor. */ + IConfigChangeListener() + {} + + /** Destructor. */ + virtual ~IConfigChangeListener() + {} }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp index 28b9a54c0..4a2f035d3 100644 --- a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp @@ -25,7 +25,7 @@ #include "layered_config_tree.h" #include "mot_error.h" -#include +#include #include @@ -55,7 +55,9 @@ public: {} ~PrintVisitor() override - {} + { + m_printList = nullptr; + } /** @brief Queries whether status is ok or error occurred. */ inline bool GetStatus() const @@ -125,7 +127,7 @@ bool LayeredConfigTree::AddConfigTree(ConfigTree* configTree) return configTreeAdded; // whether succeeded or not, we are done } else if (currConfigTree->GetPriority() < configTree->GetPriority()) { // not found yet, go on to next list - ++itr; + (void)++itr; } else { // the priority of this list is too large, so we need to add a new list before the current one configTreeAdded = AddNewConfigTreeAt(itr, configTree); @@ -163,9 +165,9 @@ void LayeredConfigTree::RemoveConfigTree(ConfigTree* configTree) ++priority; // for debug printing } else { MOT_LOG_TRACE("Removing configuration tree %s from layered configuration tree", configTree->GetSource()); - configTreeList.erase(itr2); + (void)configTreeList.erase(itr2); if (configTreeList.empty()) { - m_configTrees.erase(itr); + (void)m_configTrees.erase(itr); } found = true; break; // stop searching @@ -275,7 +277,7 @@ void LayeredConfigTree::ClearConfigTrees() m_printList.clear(); } -bool LayeredConfigTree::AddNewConfigTreeAt(LayeredConfigTrees::iterator itr, ConfigTree* configTree) +bool LayeredConfigTree::AddNewConfigTreeAt(const LayeredConfigTrees::iterator& itr, ConfigTree* configTree) { bool configTreeAdded = false; LayeredConfigTrees::pairib pb = m_configTrees.insert(itr, ConfigTreeList()); diff --git a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.h b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.h index 809775e43..dd40576b5 100644 --- a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.h +++ b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.h @@ -226,9 +226,10 @@ public: fullPathName, defaultValueStr); } else { - MOT_LOG_ERROR("Unexpected non-integer item value type %s, using default value: %s", - fullPathName, + MOT_LOG_ERROR("Unexpected non-integer item value type (%s) for configuration item %s, " + "using default value: %s", ConfigValueTypeToString(configValue->GetConfigValueType()), + fullPathName, TypeFormatter::ToString(defaultValue, stringValue)); } } @@ -332,7 +333,7 @@ private: } // check empty string, regardless of value type correctness - ConfigValue* configValue = (ConfigValue*)configItem; + const ConfigValue* configValue = static_cast(configItem); if ((configValue->GetConfigValueType() == ConfigValueType::CONFIG_VALUE_STRING) && (((const StringConfigValue*)configValue)->GetValue().length() == 0)) { // warning is printed in a nicer way be caller @@ -460,7 +461,7 @@ private: * @param configTree The configuration tree to insert. * @return True if insertion succeeded, otherwise false. */ - bool AddNewConfigTreeAt(LayeredConfigTrees::iterator itr, ConfigTree* configTree); + bool AddNewConfigTreeAt(const LayeredConfigTrees::iterator& itr, ConfigTree* configTree); /** * @brief Adds a configuration tree to an existing list of configuration trees. diff --git a/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp index abb8b1474..35a8de491 100644 --- a/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp @@ -51,8 +51,14 @@ static bool ParsePropsSectionName(const mot_string& line, mot_string& sectionPat keyValuePart.trim(); } } else { - sectionPath.assign(""); - keyValuePart.assign(line); + if (!sectionPath.assign("")) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to assign to string sectionPath"); + return false; + } + if (!keyValuePart.assign(line)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to assign to string keyValuePart"); + return false; + } keyValuePart.trim(); } } @@ -177,6 +183,12 @@ ConfigTree* PropsConfigFileLoader::LoadConfigFile(const char* configFilePath) return configTree; } + if (!reader.IsInitialized()) { + MOT_LOG_WARN("Failed to load all data from configuration file %s: out of memory", configFilePath); + // we return an empty tree to avoid errors during startup, but a warning is still issued + return configTree; + } + while (!reader.Eof() && !parseError) { // parse next non-empty line if (!line.assign(reader.GetLine().c_str())) { @@ -202,7 +214,7 @@ ConfigTree* PropsConfigFileLoader::LoadConfigFile(const char* configFilePath) // parse the key-value part if (!ConfigFileParser::ParseKeyValue( - keyValuePart, sectionFullName, key, value, arrayIndex, hasArrayIndex)) { + keyValuePart, sectionFullName, key, value, arrayIndex, hasArrayIndex)) { // key-value line malformed PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INVALID_CFG, "Failed to parse configuration file %s at line %u: %s (key/value malformed)", @@ -226,7 +238,7 @@ ConfigTree* PropsConfigFileLoader::LoadConfigFile(const char* configFilePath) } } else { if (!AddPropsArrayConfigItem( - configFilePath, reader, line, currentSection, sectionFullName, key, value, arrayIndex)) { + configFilePath, reader, line, currentSection, sectionFullName, key, value, arrayIndex)) { PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, "Failed to add array item with arrayIndex %lu in configuration file %s at line %u: %s", arrayIndex, diff --git a/src/gausskernel/storage/mot/core/infra/config/typed_config_value.h b/src/gausskernel/storage/mot/core/infra/config/typed_config_value.h index 1ec7c319a..fc0485b38 100644 --- a/src/gausskernel/storage/mot/core/infra/config/typed_config_value.h +++ b/src/gausskernel/storage/mot/core/infra/config/typed_config_value.h @@ -120,7 +120,7 @@ template > static V* CreateConfigValue(const char* path, const char* name, const T& value) { V* result = ConfigItem::CreateConfigItem(path, name, value); - if (!result->IsValid()) { + if (result != nullptr && !result->IsValid()) { delete result; result = nullptr; } @@ -359,8 +359,11 @@ struct ConfigItemClassMapper { public: \ static inline const char* ToString(const typeName& value, mot_string& stringValue) \ { \ - stringValue.format(formatStr, value); \ - return stringValue.c_str(); \ + if (stringValue.format(formatStr, value)) { \ + return stringValue.c_str(); \ + } else { \ + return ""; \ + } \ } \ static inline bool FromString(const char* stringValue, typeName& value) \ { \ @@ -391,8 +394,8 @@ public: */ static inline const char* ToString(const bool& value, mot_string& stringValue) { - stringValue.format("%s", value ? "true" : "false"); - return stringValue.c_str(); + // we avoid formatting altogether and just use constant string literals + return value ? "true" : "false"; } /** diff --git a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp index 684866523..c434fda41 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp +++ b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp @@ -23,7 +23,6 @@ */ #include "bitmapset.h" -#include "debug_utils.h" #include "global.h" #include #include @@ -40,7 +39,9 @@ BitmapSet::BitmapSet(uint8_t* data, uint16_t size) : m_data(data), m_size(size), {} BitmapSet::~BitmapSet() -{} +{ + m_data = nullptr; +} void BitmapSet::Init(uint8_t* data, uint16_t size) { @@ -70,7 +71,7 @@ void BitmapSet::Clear() securec_check(erc, "\0", "\0"); } -bool BitmapSet::IsClear() +bool BitmapSet::IsClear() const { uint16_t numBytes = GetLength(); for (uint16_t i = 0; i < numBytes; i++) @@ -83,19 +84,19 @@ bool BitmapSet::IsClear() void BitmapSet::SetBit(uint16_t bit) { MOT_ASSERT(bit < m_size); - m_data[GetByteIndex(bit)] |= (1 << (bit & 0x07)); + m_data[GetByteIndex(bit)] |= (1U << (bit & 0x07)); } void BitmapSet::UnsetBit(uint16_t bit) { MOT_ASSERT(bit < m_size); - m_data[GetByteIndex(bit)] &= ~(1 << (bit & 0x07)); + m_data[GetByteIndex(bit)] &= ~(1U << (bit & 0x07)); } -uint8_t BitmapSet::GetBit(uint16_t bit) +bool BitmapSet::GetBit(uint16_t bit) const { MOT_ASSERT(bit < m_size); - return (m_data[GetByteIndex(bit)] & (1 << (bit & 0x07))) != 0; + return (m_data[GetByteIndex(bit)] & (1U << (bit & 0x07))) != 0; } uint16_t BitmapSet::GetByteIndex(uint16_t bit) @@ -103,7 +104,7 @@ uint16_t BitmapSet::GetByteIndex(uint16_t bit) return (bit >> 3); } -void BitmapSet::operator|=(BitmapSet bitmapSet) +void BitmapSet::operator|=(const BitmapSet& bitmapSet) { uint16_t length = GetLength(); for (uint16_t i = 0; i < length; i++) { @@ -111,7 +112,7 @@ void BitmapSet::operator|=(BitmapSet bitmapSet) } } -void BitmapSet::operator&=(BitmapSet bitmapSet) +void BitmapSet::operator&=(const BitmapSet& bitmapSet) { uint16_t length = GetLength(); for (uint16_t i = 0; i < length; i++) { @@ -122,11 +123,14 @@ void BitmapSet::operator&=(BitmapSet bitmapSet) BitmapSet::BitmapSetIterator::BitmapSetIterator(const BitmapSet& bitmapSet) : m_bms(&bitmapSet), m_data(m_bms->m_data), m_bitIndex(-1), m_byteCache(0), m_isSetCache(false) { - Next(); + (void)Next(); } BitmapSet::BitmapSetIterator::~BitmapSetIterator() -{} +{ + m_bms = nullptr; + m_data = nullptr; +} bool BitmapSet::BitmapSetIterator::Start() { diff --git a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.h b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.h index 24634a612..6a13e7bba 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.h +++ b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.h @@ -25,7 +25,8 @@ #ifndef MOT_BITMAPSET_H #define MOT_BITMAPSET_H -#include +#include +#include "debug_utils.h" namespace MOT { class BitmapSet { @@ -64,8 +65,8 @@ public: void Reset(uint16_t size); void SetBit(uint16_t bit); void UnsetBit(uint16_t bit); - uint8_t GetBit(uint16_t bit); - bool IsClear(); + bool GetBit(uint16_t bit) const; + bool IsClear() const; inline uint8_t* GetData() { @@ -79,7 +80,7 @@ public: { return m_init; } - inline uint16_t GetLength() + inline uint16_t GetLength() const { return GetLength(m_size); } @@ -89,8 +90,14 @@ public: return GetByteIndex(numBits) + 1; } - void operator|=(BitmapSet bitmapSet); - void operator&=(BitmapSet bitmapSet); + inline void AddBit() + { + MOT_ASSERT(((m_size >> 3) + 1) == (((m_size + 1) >> 3) + 1)); + m_size += 1; + } + + void operator|=(const BitmapSet& bitmapSet); + void operator&=(const BitmapSet& bitmapSet); private: uint8_t* m_data; diff --git a/src/gausskernel/storage/mot/core/infra/containers/concurrent_map.h b/src/gausskernel/storage/mot/core/infra/containers/concurrent_map.h index 4c46d3c00..eb62ba8b8 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/concurrent_map.h +++ b/src/gausskernel/storage/mot/core/infra/containers/concurrent_map.h @@ -49,12 +49,12 @@ public: * @param rwlock The lock to manage. * @param lock_mode Specifies whether locking for read or write. */ - inline ScopedRWLock(pthread_rwlock_t* rwlock, RWLockMode lock_mode) : _rwlock(rwlock) + ScopedRWLock(pthread_rwlock_t* rwlock, RWLockMode lock_mode) : _rwlock(rwlock) { if (lock_mode == RWLockRead) { - pthread_rwlock_rdlock(_rwlock); + (void)pthread_rwlock_rdlock(_rwlock); } else { - pthread_rwlock_wrlock(_rwlock); + (void)pthread_rwlock_wrlock(_rwlock); } } @@ -63,17 +63,11 @@ public: */ ~ScopedRWLock() { - pthread_rwlock_unlock(_rwlock); + (void)pthread_rwlock_unlock(_rwlock); _rwlock = nullptr; } }; -/** @define Helper macro for scoped-locking a read-write lock for reading purposes (shared-access). */ -#define SCOPED_RWLOCK_READ(rwlock) ScopedRWLock _scoped_read_lock(rwlock, ScopedRWLock::RWLockRead); - -/** @define Helper macro for scoped-locking a read-write lock for writing purposes (unique access). */ -#define SCOPED_RWLOCK_WRITE(rwlock) ScopedRWLock _scoped_read_lock(rwlock, ScopedRWLock::RWLockWrite); - /** * @class ConcurrentMap * @brief General purpose concurrent map based on simple read-write lock. @@ -97,13 +91,13 @@ public: /** @brief Constructor. */ ConcurrentMap() { - pthread_rwlock_init(&_rwlock, NULL); + (void)pthread_rwlock_init(&_rwlock, NULL); } /** @brief Destruct. */ ~ConcurrentMap() { - pthread_rwlock_destroy(&_rwlock); + (void)pthread_rwlock_destroy(&_rwlock); } /** @@ -115,7 +109,7 @@ public: */ inline bool insert(const id_t& id, obj_t& obj) { - SCOPED_RWLOCK_WRITE(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockWrite); std::pair pairib = _obj_map.insert(typename obj_map_t::value_type(id, obj)); return pairib.second; } @@ -128,7 +122,7 @@ public: */ inline bool get(const id_t& id, obj_t* obj) { - SCOPED_RWLOCK_READ(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockRead); bool result = false; map_itr_t itr = _obj_map.find(id); if (itr != _obj_map.end()) { @@ -145,11 +139,11 @@ public: */ inline bool remove(const id_t& id) { - SCOPED_RWLOCK_WRITE(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockWrite); bool result = false; map_itr_t itr = _obj_map.find(id); if (itr != _obj_map.end()) { - _obj_map.erase(itr); + (void)_obj_map.erase(itr); result = true; } return result; @@ -171,7 +165,7 @@ public: */ inline size_t size() { - SCOPED_RWLOCK_READ(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockRead); return _obj_map.size(); } @@ -181,7 +175,7 @@ public: */ inline bool empty() { - SCOPED_RWLOCK_READ(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockRead); return _obj_map.empty(); } @@ -191,7 +185,7 @@ public: /** @brief Thread-safe visiting of the container elements. */ inline void for_each(functor f) { - SCOPED_RWLOCK_READ(&_rwlock); + ScopedRWLock _scoped_lock(&_rwlock, ScopedRWLock::RWLockRead); map_itr_t itr = _obj_map.begin(); while (itr != _obj_map.end()) { f(itr->first, itr->second); diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_list.h b/src/gausskernel/storage/mot/core/infra/containers/mot_list.h index 73ef5188c..dd1f414bb 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_list.h +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_list.h @@ -42,7 +42,7 @@ namespace MOT { template struct mot_list_element { /** @brief Default constructor. */ - mot_list_element() : _prev(nullptr), _next(nullptr) + mot_list_element() : _item(), _prev(nullptr), _next(nullptr) {} /** @brief Destructor. */ @@ -50,6 +50,7 @@ struct mot_list_element { { _prev = nullptr; _next = nullptr; + SetNullIfPtr(_item); } /** @brief Destroys recursively the element list. */ @@ -95,7 +96,8 @@ private: public: /** @brief Copy constructor. */ - mot_list_iterator(const mot_list_iterator& other) : _itr(other._itr) + mot_list_iterator(const mot_list_iterator& other) + : std::iterator(other), _itr(other._itr) {} /** @@ -184,7 +186,8 @@ private: public: /** @brief Copy constructor. */ - mot_list_const_iterator(const mot_list_const_iterator& other) : _itr(other._itr) + mot_list_const_iterator(const mot_list_const_iterator& other) + : std::iterator(other), _itr(other._itr) {} /** @@ -547,7 +550,7 @@ private: MOT_ERROR_RESOURCE_LIMIT, "", "Cannot allocate list item, reached size limit %u", _size_limit); } else { void* buf = Allocator::allocate(sizeof(element_type)); - if (!buf) { + if (buf == nullptr) { mot_infra_report_error(MOT_ERROR_OOM, "", "Failed to allocate list item"); } else { result = new (buf) element_type(); diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_map.h b/src/gausskernel/storage/mot/core/infra/containers/mot_map.h index 906d9b721..6af75ef1c 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_map.h +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_map.h @@ -48,11 +48,6 @@ public: /** @typedef The map implementation (currently a simple list of key-value pairs). */ typedef mot_list, Allocator, mot_pair_assigner > map_impl; -private: - /** @var The key-value map. */ - map_impl _map; - -public: /** @brief Default constructor. */ mot_map() {} @@ -195,6 +190,10 @@ public: { return _map.cend(); } + +private: + /** @var The key-value map. */ + map_impl _map; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_set.h b/src/gausskernel/storage/mot/core/infra/containers/mot_set.h index ddc1138fb..464747ddb 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_set.h +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_set.h @@ -44,11 +44,6 @@ public: /** @typedef The set implementation (currently a simple list of items). */ typedef mot_list set_impl; -private: - /** @var The set of items. */ - set_impl _set; - -public: /** @brief Consturctor. */ mot_set() {} @@ -188,6 +183,10 @@ public: { return _set.cend(); } + +private: + /** @var The set of items. */ + set_impl _set; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_string.cpp b/src/gausskernel/storage/mot/core/infra/containers/mot_string.cpp index c4b01131b..056c6a920 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_string.cpp +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_string.cpp @@ -25,9 +25,9 @@ #include "mot_string.h" #include "infra_util.h" -#include -#include -#include +#include +#include +#include namespace MOT { template <> diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_string.h b/src/gausskernel/storage/mot/core/infra/containers/mot_string.h index d5bdcddaa..eba062174 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_string.h +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_string.h @@ -30,11 +30,11 @@ #include "infra.h" #include "mot_error_codes.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include @@ -52,6 +52,8 @@ public: * @brief Default constructor. * @param size_limit The upper size limit for the string length. * @param capacity The initial capacity for the string. + * @note Caller needs to call @ref is_valid() in order to validate that the constructor allocated memory + * successfully as requested. */ explicit mot_basic_string(uint32_t sizeLimit = DEFAULT_SIZE_LIMIT, uint32_t capacity = INIT_CAPACITY) : _str(nullptr), _size(0), _capacity(0), _size_limit(sizeLimit) @@ -68,6 +70,8 @@ public: /** * @brief Copy constructor. * @param other The object to copy the string from. + * @note Caller needs to call @ref is_valid() in order to validate that the constructor allocated memory + * successfully as requested. */ mot_basic_string(const mot_basic_string& other) : mot_basic_string(other._size_limit, other._capacity) { @@ -91,27 +95,36 @@ public: /** * @brief Constructs a string object from another null-terminated c-string. * @param str The string with which the object is to be initialized. + * @note Caller needs to call @ref is_valid() in order to validate that the constructor allocated memory + * successfully as requested. */ mot_basic_string(const char* str) : mot_basic_string() // call default constructor { - assign(str); + if (!assign(str)) { + mot_infra_report_error(MOT_ERROR_OOM, "", "Failed to construct string: %s", str); + } } /** * @brief Constructs a string object from another c-string. * @param str The string with which the object is to be initialized. * @param n The number of character to copy from the source string. + * @note Caller needs to call @ref is_valid() in order to validate that the constructor allocated memory + * successfully as requested. */ mot_basic_string(const char* str, size_t n) : mot_basic_string() // call default constructor { - assign(str, n); + if (!assign(str, n)) { + mot_infra_report_error(MOT_ERROR_OOM, "", "Failed to construct %zu chars string: %.*s", n, n, str); + } } /** @brief Destructor. */ ~mot_basic_string() noexcept { - if (_str) { + if (_str != nullptr) { Allocator::free(_str); + _str = nullptr; } } @@ -297,7 +310,7 @@ public: } else { uint32_t trailingWhitespaceCount = count_trail_ws(); uint32_t orgSize = _size; - _size = _size - leadingWhitespaceCount - trailingWhitespaceCount; + _size = (_size - leadingWhitespaceCount) - trailingWhitespaceCount; errno_t erc = memmove_s(_str, orgSize, _str + leadingWhitespaceCount, _size); securec_check(erc, "\0", "\0"); } @@ -478,8 +491,8 @@ public: upto_pos = _size; } for (uint32_t i = 0; i < upto_pos; ++i) { - if (_str[upto_pos - i - 1] == c) { - return upto_pos - i - 1; + if (_str[(upto_pos - i) - 1] == c) { + return (upto_pos - i) - 1; } } return npos; @@ -583,7 +596,7 @@ private: _size_limit); } else { // align and check with limit - uint32_t alignedCapacity = (new_capacity + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT; + uint32_t alignedCapacity = ((new_capacity + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; if (alignedCapacity > _size_limit) { mot_infra_report_error(MOT_ERROR_RESOURCE_LIMIT, "", @@ -615,7 +628,7 @@ private: } else { newPtr = Allocator::allocate(selectedCapacity); } - if (newPtr != NULL) { + if (newPtr != nullptr) { _str = (char*)newPtr; _capacity = selectedCapacity; result = true; diff --git a/src/gausskernel/storage/mot/core/infra/containers/mot_vector.h b/src/gausskernel/storage/mot/core/infra/containers/mot_vector.h index eb5794872..b3b1e736e 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/mot_vector.h +++ b/src/gausskernel/storage/mot/core/infra/containers/mot_vector.h @@ -225,7 +225,7 @@ public: * @param capacity The initial capacity for the vector. */ mot_vector(uint32_t size_limit = DEFAULT_SIZE_LIMIT, uint32_t capacity = INIT_CAPACITY) - : _array(nullptr), _size(0), _capacity(0), _size_limit(size_limit) + : _array(nullptr), _size(0), _capacity(capacity), _size_limit(size_limit) { // we initialize capacity member to zero on purpose because vector array has not been allocated yet if (_capacity > 0) { @@ -292,16 +292,16 @@ public: } /** @brief Retrieves a modifiable reference to the item at the specified position. */ - T& at(int pos) + T& at(uint32_t pos) { - MOT_INFRA_ASSERT(pos < (int)_size); + MOT_INFRA_ASSERT(pos < _size); return _array[pos]; } /** @brief Retrieves a non-modifiable reference to the item at the specified position. */ - const T& at(int pos) const + const T& at(uint32_t pos) const { - MOT_INFRA_ASSERT(pos < (int)_size); + MOT_INFRA_ASSERT(pos < _size); return _array[pos]; } @@ -336,7 +336,7 @@ public: // move items uint32_t items_to_move = _size - pos.get_pos(); for (uint32_t i = 0; i < items_to_move; ++i) { - Assigner::move(_array[_size - i], std::move(_array[_size - i - 1])); + Assigner::move(_array[_size - i], std::move(_array[(_size - i) - 1])); } result = Assigner::assign(_array[pos.get_pos()], item); if (!result) { @@ -364,13 +364,13 @@ public: } /** @brief Retrieves a modifiable reference to the item at the specified position. */ - inline T& operator[](int pos) + inline T& operator[](uint32_t pos) { return at(pos); } /** @brief Retrieves a non-modifiable reference to the item at the specified position. */ - inline const T& operator[](int pos) const + inline const T& operator[](uint32_t pos) const { return at(pos); } @@ -412,13 +412,13 @@ public: } /** @brief Retrieves a modifying iterator pointing to the item in the specified position in the vector. */ - inline iterator itr_at(int pos) + inline iterator itr_at(uint32_t pos) { return iterator(_array, pos); } /** @brief Retrieves a non-modifying iterator pointing to the item in the specified position in the vector. */ - inline const_iterator citr_at(int pos) const + inline const_iterator citr_at(uint32_t pos) const { return const_iterator(_array, pos); } @@ -440,7 +440,7 @@ private: _size_limit); } else { // align and check with limit - uint32_t aligned_capacity = (new_capacity + GROW_SIZE - 1) / GROW_SIZE * GROW_SIZE; + uint32_t aligned_capacity = ((new_capacity + GROW_SIZE - 1) / GROW_SIZE) * GROW_SIZE; if (aligned_capacity > _size_limit) { mot_infra_report_error(MOT_ERROR_RESOURCE_LIMIT, "", diff --git a/src/gausskernel/storage/mot/core/infra/containers/spsc_queue.h b/src/gausskernel/storage/mot/core/infra/containers/spsc_queue.h new file mode 100644 index 000000000..9c74e5513 --- /dev/null +++ b/src/gausskernel/storage/mot/core/infra/containers/spsc_queue.h @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * spsc_queue.h + * A single producer/single consumer queue + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/infra/containers/spsc_queue.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef SPSCQUEUE_H +#define SPSCQUEUE_H + +#include +#include +#include +#include "mot_atomic_ops.h" +#include "utilities.h" + +namespace MOT { +#define COUNT(head, tail, mask) ((uint32_t)(((head) - (tail)) & (mask))) +#define SPACE(head, tail, mask) ((uint32_t)(((tail) - ((head) + 1)) & (mask))) + +const uint32_t MAX_REDO_QUE_TAKE_DELAY = 200; /* 100 us */ +const uint32_t MAX_REDO_QUE_IDEL_TAKE_DELAY = 1000; + +const uint32_t QUEUE_CAPACITY_MIN_LIMIT = 2; +const uint32_t QUEUE_PUT_WAIT_LIMIT = 3; +const uint32_t QUEUE_TAKE_WAIT_LIMIT = 3; + +template +class SPSCQueue { +public: + SPSCQueue(uint32_t capacity) + : m_initialized(false), + m_writeHead(0), + m_readTail(0), + m_capacity(capacity), + m_mask(capacity - 1), + m_maxUsage(0), + m_totalCnt(0), + m_buffer(nullptr) + { + /* + * We require the capacity to be a power of 2, so index wrap can be + * handled by a bit-wise and. The actual capacity is one less than + * the specified, so the minimum capacity is 2. + */ + assert(capacity >= QUEUE_CAPACITY_MIN_LIMIT && POWER_OF_TWO(capacity)); + } + + SPSCQueue(const SPSCQueue& orig) = delete; + + virtual ~SPSCQueue() + { + if (m_buffer != nullptr) { + free(m_buffer); + m_buffer = nullptr; + } + m_initialized = false; + } + + bool Init() + { + size_t allocSize = sizeof(void*) * m_capacity; + m_buffer = (void**)malloc(allocSize); + if (m_buffer != nullptr) { + m_initialized = true; + } + return m_initialized; + } + + bool Put(T* data) + { + uint32_t head; + uint32_t tail; + uint64_t cnt = 0; + uint32_t tmpCnt = 0; + head = m_writeHead.load(); + do { + if (cnt > QUEUE_PUT_WAIT_LIMIT) { + return false; + } + tail = m_readTail.load(); + cnt++; + } while (SPACE(head, tail, m_mask) == 0); + + /* + * Make sure the following write to the buffer happens after the read + * of the tail. Combining this with the corresponding barrier in Take() + * which guarantees that the tail is updated after reading the buffer, + * we can be sure that we cannot update a slot's value before it has + * been read. + */ + MEMORY_BARRIER(); + + tmpCnt = COUNT(head, tail, m_mask); + if (tmpCnt > m_maxUsage) { + m_maxUsage.store(tmpCnt); + } + + m_buffer[head] = static_cast(data); + + /* Make sure the index is updated after the buffer has been written. */ + WRITE_BARRIER(); + + m_writeHead.store((head + 1) & m_mask); + return true; + } + + T* Take() + { + uint32_t head = m_writeHead.load(); + uint32_t tail = m_readTail.load(); + if (COUNT(head, tail, m_mask) == 0) { + return nullptr; + } + + /* Make sure the buffer is read after the index. */ + READ_BARRIER(); + + T* elem = static_cast(m_buffer[tail]); + + /* Make sure the read of the buffer finishes before updating the tail. */ + MEMORY_BARRIER(); + + m_readTail.store((tail + 1) & m_mask); + return elem; + } + + bool IsEmpty() + { + return Count() == 0; + } + + T* Top() + { + uint32_t head = m_writeHead.load(); + uint32_t tail = m_readTail.load(); + if (COUNT(head, tail, m_mask) == 0) { + return nullptr; + } + + READ_BARRIER(); + T* elem = static_cast(m_buffer[tail]); + return elem; + } + + void Pop() + { + uint32_t head = m_writeHead.load(); + uint32_t tail = m_readTail.load(); + uint64_t totalCnt = m_totalCnt.load(); + if (COUNT(head, tail, m_mask) == 0) { + return; + } + + /* Make sure the read of the buffer finishes before updating the tail. */ + MEMORY_BARRIER(); + m_totalCnt.store(totalCnt + 1); + m_readTail.store((tail + 1) & m_mask); + } + + uint32_t Count() + { + uint32_t head = m_writeHead.load(); + uint32_t tail = m_readTail.load(); + return (COUNT(head, tail, m_mask)); + } + +private: + bool m_initialized; + std::atomic m_writeHead; /* Array index for the next write. */ + std::atomic m_readTail; /* Array index for the next read. */ + uint32_t m_capacity; /* Queue capacity, must be power of 2. */ + uint32_t m_mask; /* Bit mask for computing index. */ + std::atomic m_maxUsage; + std::atomic m_totalCnt; + void** m_buffer; /* Queue buffer, the actual size is capacity. */ +}; +} // namespace MOT + +#endif /* SPSCQUEUE_H */ diff --git a/src/gausskernel/storage/mot/core/infra/infra.cpp b/src/gausskernel/storage/mot/core/infra/infra.cpp index 7d161c620..efb7f7a35 100644 --- a/src/gausskernel/storage/mot/core/infra/infra.cpp +++ b/src/gausskernel/storage/mot/core/infra/infra.cpp @@ -26,7 +26,7 @@ #include "mot_error.h" #include "logger.h" -#include +#include namespace MOT { DECLARE_LOGGER(Infra, Infra) diff --git a/src/gausskernel/storage/mot/core/infra/infra.h b/src/gausskernel/storage/mot/core/infra/infra.h index 875ba05f4..fa6320bd2 100644 --- a/src/gausskernel/storage/mot/core/infra/infra.h +++ b/src/gausskernel/storage/mot/core/infra/infra.h @@ -25,8 +25,8 @@ #ifndef INFRA_H #define INFRA_H -#include -#include +#include +#include #include #include "debug_utils.h" @@ -192,7 +192,7 @@ public: template struct mot_pair { /** @brief Default constructor. */ - mot_pair() + mot_pair() : first(), second() {} /** diff --git a/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp b/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp index f3d62069c..168ce5020 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp @@ -94,9 +94,9 @@ mot_string GlobalStatistics::MakeName(const char* baseName, NamingScheme namingS if ((namingScheme == NamingScheme::NAMING_SCHEME_TOTAL) || (namingScheme == NamingScheme::NAMING_SCHEME_TOTAL_PREV)) { - result.format("%s[TOTAL]", baseName); + (void)result.format("%s[TOTAL]", baseName); } else if (namingScheme == NamingScheme::NAMING_SCHEME_DIFF) { - result.format("%s[DIFF]", baseName); + (void)result.format("%s[DIFF]", baseName); } return result; diff --git a/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp index 637573810..46b535125 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp @@ -42,9 +42,9 @@ void LevelStatisticVariable::Summarize(bool updateTstamp) void LevelStatisticVariable::Print(LogLevel logLevel) const { MOT_LOG(logLevel, - "%s={ current level: %" PRId64 " %s, peak: %" PRIu64 " %s [%" PRIu64 " samples] }", + "%s={ current level: %" PRIu64 " %s, peak: %" PRIu64 " %s [%" PRIu64 " samples] }", m_name, - m_levelSaved / m_factor, + static_cast(m_levelSaved) / m_factor, m_units, m_peakSaved / m_factor, m_units, diff --git a/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp index ee60df4ab..f87bfd0cb 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp @@ -33,7 +33,7 @@ void MemoryStatisticVariable::Print(LogLevel logLevel) const void MemoryStatisticVariable::Assign(const StatisticVariable& rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + const MemoryStatisticVariable& memRhs = static_cast(rhs); m_count = memRhs.m_count; m_level.Assign(memRhs.m_level); m_rate.Assign(memRhs.m_rate); @@ -41,7 +41,7 @@ void MemoryStatisticVariable::Assign(const StatisticVariable& rhs) void MemoryStatisticVariable::Add(const StatisticVariable& rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + const MemoryStatisticVariable& memRhs = static_cast(rhs); m_count += memRhs.m_count; m_level.Add(memRhs.m_level); m_rate.Add(memRhs.m_rate); @@ -49,7 +49,7 @@ void MemoryStatisticVariable::Add(const StatisticVariable& rhs) void MemoryStatisticVariable::Subtract(const StatisticVariable& rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + const MemoryStatisticVariable& memRhs = static_cast(rhs); m_count -= memRhs.m_count; m_level.Subtract(memRhs.m_level); m_rate.Subtract(memRhs.m_rate); diff --git a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp index 23c0e371b..df9e3a274 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp @@ -22,8 +22,8 @@ * ------------------------------------------------------------------------- */ -#include -#include +#include +#include #include "numeric_statistic_variable.h" @@ -32,6 +32,7 @@ NumericStatisticVariable::NumericStatisticVariable( const char* name, uint64_t factor /* = 1 */, const char* units /* = "" */, uint64_t limit /* = ULONG_LONG_MAX */) : StatisticVariable(name), m_factor(factor), + m_units{0}, m_limit(limit), m_sum(0), m_squareSum(0), diff --git a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.h b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.h index e54ba5ef4..1a39c79e5 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.h +++ b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.h @@ -27,7 +27,7 @@ #include "statistic_variable.h" -#include +#include namespace MOT { /** diff --git a/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp index d9d74d5d2..19b224def 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp @@ -36,7 +36,7 @@ void RateStatisticVariable::Summarize(bool updateTstamp) m_countSaved = m_count; m_sumSaved = m_sum; m_intervalSeconds = CpuCyclesLevelTime::CyclesToSeconds(m_tstamp - m_initTstamp); - m_rate = ((double)(m_sumSaved)) / m_intervalSeconds / m_factor; + m_rate = (((double)(m_sumSaved)) / m_intervalSeconds) / m_factor; m_frequency = ((double)(m_countSaved)) / m_intervalSeconds; } diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistic_variable.h b/src/gausskernel/storage/mot/core/infra/stats/statistic_variable.h index 6d964c5b9..90d463bb4 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistic_variable.h +++ b/src/gausskernel/storage/mot/core/infra/stats/statistic_variable.h @@ -42,7 +42,7 @@ public: * @brief Constructor. * @param name Name used for printing. */ - explicit StatisticVariable(const char* name) : m_count(0) + explicit StatisticVariable(const char* name) : m_name{0}, m_count(0) { SetName(name); } diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp index c5eb1645c..2f3e2a2be 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp @@ -80,7 +80,7 @@ static bool CreateRecursiveMutex(pthread_mutex_t* mutex) result = true; } } - pthread_mutexattr_destroy(&lockattr); + (void)pthread_mutexattr_destroy(&lockattr); } return result; @@ -108,7 +108,7 @@ bool StatisticsManager::Initialize() m_initPhase = INIT_STAT_PRINT_CV; // register to configuration change notifications - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } while (0); if (result) { @@ -121,6 +121,7 @@ bool StatisticsManager::Initialize() StatisticsManager::StatisticsManager() : m_statsPrintPeriodSeconds(GetGlobalConfiguration().m_statPrintPeriodSeconds), m_fullStatsPrintPeriodSeconds(GetGlobalConfiguration().m_statPrintFullPeriodSeconds), + m_statsThread(0), m_running(false), m_initPhase(INIT) { @@ -134,16 +135,16 @@ StatisticsManager::~StatisticsManager() // stop the statistics printing thread StopStatsPrintThread(); // unregister from configuration change notifications - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); // fall through case INIT_STAT_PRINT_CV: - pthread_cond_destroy(&m_statsPrintCond); + (void)pthread_cond_destroy(&m_statsPrintCond); // fall through case INIT_STAT_PRINT_LOCK: - pthread_mutex_destroy(&m_statsPrintLock); + (void)pthread_mutex_destroy(&m_statsPrintLock); // fall through case INIT_PROVIDERS_LOCK: - pthread_mutex_destroy(&m_providersLock); + (void)pthread_mutex_destroy(&m_providersLock); // fall through case INIT: default: @@ -189,7 +190,7 @@ StatisticsManager& StatisticsManager::GetInstance() bool StatisticsManager::RegisterStatisticsProvider(StatisticsProvider* statisticsProvider) { bool result = false; - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); if (itr == m_providers.end()) { @@ -204,23 +205,23 @@ bool StatisticsManager::RegisterStatisticsProvider(StatisticsProvider* statistic } } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); return result; } bool StatisticsManager::UnregisterStatisticsProvider(StatisticsProvider* statisticsProvider) { bool result = false; - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); if (itr != m_providers.end()) { - m_providers.erase(itr); + (void)m_providers.erase(itr); MOT_LOG_TRACE("Unregistered statistics provider: %s", statisticsProvider->GetName()); result = true; } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); return result; } @@ -264,7 +265,7 @@ struct StatisticsProviderPrinter { StatisticsProvider* StatisticsManager::GetStatisticsProvider(const char* name) { StatisticsProvider* result = nullptr; - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); mot_list::iterator itr = find_if(m_providers.begin(), m_providers.end(), StatisticsProviderFinder(name)); @@ -272,7 +273,7 @@ StatisticsProvider* StatisticsManager::GetStatisticsProvider(const char* name) result = *itr; } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); return result; } @@ -286,7 +287,7 @@ bool StatisticsManager::ReserveThreadSlot() "Reserve Thread Slot for Statistics", "Invalid attempt to reserve statistics thread slot without current thread identifier denied"); } else { - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); result = true; MOT_LOG_TRACE("Reserving statistics thread slot for thread id %" PRIu16, tid); @@ -300,7 +301,7 @@ bool StatisticsManager::ReserveThreadSlot() ++itr; } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); } return result; @@ -312,7 +313,7 @@ void StatisticsManager::UnreserveThreadSlot() if (tid == INVALID_THREAD_ID) { MOT_LOG_ERROR("Invalid attempt to un-reserve statistics thread slot without current thread identifier denied"); } else { - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); MOT_LOG_TRACE("Un-reserving statistics thread slot for thread id %" PRIu16, tid); mot_list::iterator itr = m_providers.begin(); @@ -322,13 +323,13 @@ void StatisticsManager::UnreserveThreadSlot() ++itr; } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); } } void StatisticsManager::PrintStatistics(LogLevel logLevel, uint32_t statOpts /* = STAT_OPT_DEFAULT */) { - pthread_mutex_lock(&m_providersLock); + (void)pthread_mutex_lock(&m_providersLock); // make sure there is at least one enabled provider with some statistics if (!m_providers.empty()) { @@ -358,12 +359,12 @@ void StatisticsManager::PrintStatistics(LogLevel logLevel, uint32_t statOpts /* } MOT_LOG(logLevel, "======================================================"); - for_each(m_providers.cbegin(), m_providers.cend(), StatisticsProviderPrinter(logLevel, statOpts)); + (void)for_each(m_providers.cbegin(), m_providers.cend(), StatisticsProviderPrinter(logLevel, statOpts)); MOT_LOG(logLevel, "======================================================"); } } - pthread_mutex_unlock(&m_providersLock); + (void)pthread_mutex_unlock(&m_providersLock); } bool StatisticsManager::StartStatsPrintThread() @@ -384,13 +385,17 @@ void StatisticsManager::StopStatsPrintThread() { // signal done flag and wake up statistics printing thread if (m_running) { - pthread_mutex_lock(&m_statsPrintLock); + (void)pthread_mutex_lock(&m_statsPrintLock); m_running = false; - pthread_cond_signal(&m_statsPrintCond); - pthread_mutex_unlock(&m_statsPrintLock); + (void)pthread_cond_signal(&m_statsPrintCond); + (void)pthread_mutex_unlock(&m_statsPrintLock); // wait for statistics printing thread to finish - pthread_join(m_statsThread, nullptr); + int rc = pthread_join(m_statsThread, nullptr); + if (rc) { + MOT_REPORT_SYSTEM_ERROR_CODE( + rc, pthread_join, "Statistics Manager Ending", "Failed to join statistics thread"); + } } } @@ -414,13 +419,11 @@ void StatisticsManager::StatsPrintThread() statsPrintCount += STAT_PRINT_CHECK_PERIOD_SECONDS; fullStatsPrintCount += STAT_PRINT_CHECK_PERIOD_SECONDS; - bool statsPrinted = false; // print periodic report if (statsPrintCount >= m_statsPrintPeriodSeconds) { PrintStatistics(LogLevel::LL_INFO, STAT_OPT_SCOPE_ALL | STAT_OPT_PERIOD_DIFF | STAT_OPT_LEVEL_SUMMARY); statsPrintCount = 0; - statsPrinted = true; } // print full report @@ -428,7 +431,6 @@ void StatisticsManager::StatsPrintThread() PrintStatistics(LogLevel::LL_INFO, STAT_OPT_SCOPE_ALL | STAT_OPT_PERIOD_TOTAL | STAT_OPT_LEVEL_SUMMARY); PrintStatistics(LogLevel::LL_INFO, STAT_OPT_SCOPE_ALL | STAT_OPT_PERIOD_TOTAL | STAT_OPT_LEVEL_DETAIL); fullStatsPrintCount = 0; - statsPrinted = true; } } @@ -442,12 +444,12 @@ void StatisticsManager::StatsPrintThread() void StatisticsManager::WaitNextPrint() { struct timeval now; - gettimeofday(&now, nullptr); + (void)gettimeofday(&now, nullptr); struct timespec ts = {(time_t)(now.tv_sec + STAT_PRINT_CHECK_PERIOD_SECONDS), now.tv_usec * 1000L}; - pthread_mutex_lock(&m_statsPrintLock); - pthread_cond_timedwait(&m_statsPrintCond, &m_statsPrintLock, &ts); - pthread_mutex_unlock(&m_statsPrintLock); + (void)pthread_mutex_lock(&m_statsPrintLock); + (void)pthread_cond_timedwait(&m_statsPrintCond, &m_statsPrintLock, &ts); + (void)pthread_mutex_unlock(&m_statsPrintLock); } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.h b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.h index bc669f4a6..f506f8efc 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.h +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.h @@ -118,7 +118,7 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; private: /** @var The single instance. */ @@ -155,7 +155,7 @@ private: StatisticsManager(); /** @brief Destructor. */ - ~StatisticsManager(); + ~StatisticsManager() override; /** * @brief Initializes the manager. diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp index b13f06997..47a75273d 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp @@ -41,6 +41,7 @@ StatisticsProvider::StatisticsProvider( m_threadStats(nullptr), m_threadStatCount(0), m_hasExtendedStats(extended), + m_statLock(0), m_globalStats(nullptr), m_aggregateStats(nullptr), m_prevAggregateStats(nullptr), @@ -95,6 +96,9 @@ StatisticsProvider::~StatisticsProvider() if (m_deadThreadStats) { delete m_deadThreadStats; } + + // Generator will be destroyed by the provider + m_generator = nullptr; } bool StatisticsProvider::Initialize() @@ -173,9 +177,9 @@ bool StatisticsProvider::ReserveThreadSlot() "Failed to allocate buffer in size of %u bytes", m_generator->GetObjectSize()); } else { - pthread_spin_lock(&m_statLock); + (void)pthread_spin_lock(&m_statLock); m_threadStats[threadId] = m_generator->CreateThreadStatistics(threadId, buffer); - pthread_spin_unlock(&m_statLock); + (void)pthread_spin_unlock(&m_statLock); MOT_LOG_DEBUG("m_threadStats[%u] = %p", (unsigned)threadId, m_threadStats[threadId]); result = true; } @@ -199,11 +203,11 @@ void StatisticsProvider::UnreserveThreadSlot() threadId); } else { // aggregate dead thread statistics and cleanup - pthread_spin_lock(&m_statLock); + (void)pthread_spin_lock(&m_statLock); ThreadStatistics* threadStats = m_threadStats[threadId]; m_deadThreadStats->Add(*threadStats); m_threadStats[threadId] = nullptr; // this must be guarded with a lock due to race with Summarize() - pthread_spin_unlock(&m_statLock); + (void)pthread_spin_unlock(&m_statLock); FreeThreadStats(threadId, threadStats); // cleanup now outside lock scope MOT_LOG_TRACE("Unreserved %s statistics thread slot for thread id %" PRIu16, GetName(), threadId); } @@ -212,7 +216,7 @@ void StatisticsProvider::UnreserveThreadSlot() void StatisticsProvider::Summarize() { - pthread_spin_lock(&m_statLock); + (void)pthread_spin_lock(&m_statLock); m_prevAggregateStats->Assign(*m_aggregateStats); m_aggregateStats->Reset(); @@ -230,7 +234,7 @@ void StatisticsProvider::Summarize() m_aggregateStats->Add(*m_deadThreadStats); m_deadThreadStats->Reset(); - pthread_spin_unlock(&m_statLock); + (void)pthread_spin_unlock(&m_statLock); // from this point onward there is no race over m_threadStats or m_deadThreadStats m_aggregateStats->Summarize(false); @@ -270,7 +274,7 @@ bool StatisticsProvider::HasStatisticsFor(uint32_t statOpts) if (!result && (statOpts & STAT_OPT_SCOPE_GLOBAL)) { if (statOpts & STAT_OPT_LEVEL_DETAIL) { - pthread_spin_lock(&m_statLock); + (void)pthread_spin_lock(&m_statLock); for (uint32_t i = 0; i < m_threadStatCount; ++i) { if (m_threadStats[i] != nullptr) { result = m_threadStats[i]->HasValidSamples(); @@ -279,7 +283,7 @@ bool StatisticsProvider::HasStatisticsFor(uint32_t statOpts) } } } - pthread_spin_unlock(&m_statLock); + (void)pthread_spin_unlock(&m_statLock); if (!result) { result = m_averageStats->HasValidSamples(); } @@ -323,13 +327,13 @@ void StatisticsProvider::PrintThreadStats(uint32_t statOpts, uint32_t statId, Lo // print total stats if (statOpts & STAT_OPT_PERIOD_TOTAL) { if (statOpts & STAT_OPT_LEVEL_DETAIL) { - pthread_spin_lock(&m_statLock); + (void)pthread_spin_lock(&m_statLock); for (uint32_t i = 0; i < m_threadStatCount; ++i) { if (m_threadStats[i] != nullptr) { m_threadStats[i]->Print(statId, logLevel); } } - pthread_spin_unlock(&m_statLock); + (void)pthread_spin_unlock(&m_statLock); m_averageStats->Print(statId, logLevel); } if (statOpts & STAT_OPT_LEVEL_SUMMARY) { @@ -338,7 +342,7 @@ void StatisticsProvider::PrintThreadStats(uint32_t statOpts, uint32_t statId, Lo } } -void StatisticsProvider::PrintGlobalStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel) +void StatisticsProvider::PrintGlobalStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel) const { // print diff stats if (statOpts & STAT_OPT_PERIOD_DIFF) { diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.h b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.h index 485e358bf..a2f6032f4 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.h +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.h @@ -73,18 +73,6 @@ constexpr uint32_t STAT_OPT_DEFAULT = (STAT_OPT_SCOPE_GLOBAL | STAT_OPT_PERIOD_T */ class StatisticsProvider { public: - /** - * @brief Constructor - * @param name The unique name that identifies the provider. - * @param generator The statistics generator. - * @param enable Specifies whether the statistics provider is enabled. - * @param[opt] extended Specifies whether extended statistics are printed by this provider. - */ - StatisticsProvider(const char* name, StatisticsGenerator* generator, bool enable, bool extended = false); - - /** @brief Destructor. */ - virtual ~StatisticsProvider(); - /** * @brief Initializes the object. * @return True if initialization succeeded, otherwise false. @@ -150,10 +138,23 @@ public: } protected: + /** + * @brief Constructor + * @param name The unique name that identifies the provider. + * @param generator The statistics generator. + * @param enable Specifies whether the statistics provider is enabled. + * @param[opt] extended Specifies whether extended statistics are printed by this provider. + */ + StatisticsProvider(const char* name, StatisticsGenerator* generator, bool enable, bool extended = false); + + /** @brief Destructor. */ + virtual ~StatisticsProvider(); + /** * @brief Allow deriving class to print more non-standard statistics */ - virtual void PrintStatisticsEx(){}; + virtual void PrintStatisticsEx() + {} /** * @brief Retrieves thread-level statistics. @@ -265,7 +266,7 @@ private: * @param statId Ordinal statistic variable identifier. * @param logLevel Printing log level. */ - void PrintGlobalStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel); + void PrintGlobalStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel) const; /** * @brief Reclaims all resources associated with a thread statistics object. diff --git a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp index 6a9ebb6bf..3711cdc5c 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp @@ -114,15 +114,15 @@ mot_string ThreadStatistics::MakeName(const char* baseName, uint64_t threadId) { mot_string result; if (threadId == THREAD_ID_TOTAL) { - result.format("%s[TOTAL]", baseName); + (void)result.format("%s[TOTAL]", baseName); } else if (threadId == THREAD_ID_AVG) { - result.format("%s[AVG]", baseName); + (void)result.format("%s[AVG]", baseName); } else if (threadId == THREAD_ID_DIFF) { - result.format("%s[DIFF]", baseName); + (void)result.format("%s[DIFF]", baseName); } else if (threadId == THREAD_ID_DIFF_AVG) { - result.format("%s[DIFF-AVG]", baseName); + (void)result.format("%s[DIFF-AVG]", baseName); } else { - result.format("%s[%u]", baseName, (unsigned)threadId); + (void)result.format("%s[%u]", baseName, (unsigned)threadId); } return result; diff --git a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.h b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.h index 0ac6d3ec8..6e96ee734 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.h +++ b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.h @@ -56,7 +56,10 @@ public: /** @brief Destructor. */ virtual ~ThreadStatistics() - {} + { + // m_inplaceBuffer is not owned by this class + m_inplaceBuffer = nullptr; + } /** * @brief Retrieves the amount of statistic variables contained by this object. diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp index 5b1f944ac..14c4a36ea 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp +++ b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include "affinity.h" #include "global.h" @@ -47,81 +47,64 @@ void Affinity::Configure(uint64_t numaNodes, uint64_t physicalCoresNuma, Affinit m_affinityMode = affinityMode; } -uint32_t Affinity::GetAffineProcessor(uint64_t threadId) const +int Affinity::GetAffineProcessor(uint64_t threadId) const { - uint32_t result = INVALID_CPU_ID; + int result = INVALID_CPU_ID; threadId = threadId % (m_numaNodes * m_physicalCoresNuma); switch (m_affinityMode) { case AffinityMode::FILL_SOCKET_FIRST: { - result = (uint32_t)GetGlobalConfiguration().GetMappedCore(threadId); + result = GetGlobalConfiguration().GetMappedCore(threadId); break; } case AffinityMode::EQUAL_PER_SOCKET: { - threadId = threadId % (m_numaNodes * m_physicalCoresNuma); - uint32_t numaId = threadId % m_numaNodes; - uint32_t localProc = threadId / m_numaNodes; - result = (uint32_t)(numaId * m_physicalCoresNuma + localProc); + int numaId = threadId % m_numaNodes; + int localProc = threadId / m_numaNodes; + result = GetGlobalConfiguration().GetCoreFromNumaNodeByIndex(numaId, localProc); break; } case AffinityMode::FILL_PHYSICAL_FIRST: - result = (uint32_t)GetGlobalConfiguration().GetCoreByConnidFP(threadId); + result = GetGlobalConfiguration().GetCoreByConnidFP((int)threadId); break; default: MOT_LOG_ERROR("%s: Invalid affinity configuration: %d", __func__, (int)m_affinityMode); - result = INVALID_CPU_ID; break; } return result; } -uint32_t Affinity::GetAffineNuma(uint64_t threadId) const +int Affinity::GetAffineNuma(uint64_t threadId) const { - uint32_t result = INVALID_NODE_ID; - threadId = threadId % (m_numaNodes * m_physicalCoresNuma); + int result = INVALID_NODE_ID; switch (m_affinityMode) { - case AffinityMode::FILL_SOCKET_FIRST: { - result = (uint32_t)GetGlobalConfiguration().GetCpuNode(GetGlobalConfiguration().GetMappedCore(threadId)); - break; - } - + case AffinityMode::FILL_SOCKET_FIRST: case AffinityMode::EQUAL_PER_SOCKET: - result = (uint32_t)threadId % m_numaNodes; - break; - case AffinityMode::FILL_PHYSICAL_FIRST: { - uint64_t realCoreCount = 0; - if (GetGlobalConfiguration().IsHyperThread() == true) { - realCoreCount = m_physicalCoresNuma / 2; - } else { - realCoreCount = m_physicalCoresNuma; - } - threadId = threadId % (m_numaNodes * realCoreCount); - result = (uint32_t)(threadId / realCoreCount); + int coreId = GetAffineProcessor(threadId); + result = GetGlobalConfiguration().GetCpuNode(coreId); break; } default: MOT_LOG_ERROR("%s: Invalid affinity configuration: %d", __func__, (int)m_affinityMode); - result = (uint32_t)INVALID_NODE_ID; break; } return result; } -bool Affinity::SetAffinity(uint64_t threadId, uint32_t* threadCore /* = nullptr */) const +bool Affinity::SetAffinity(uint64_t threadId, int* threadCore /* = nullptr */) const { bool result = true; - uint32_t coreId = GetAffineProcessor(threadId); + int coreId = GetAffineProcessor(threadId); cpu_set_t mask; CPU_ZERO(&mask); - GetGlobalConfiguration().SetMaskToAllCoresinNumaSocket(mask, coreId); + GetGlobalConfiguration().SetMaskToAllCoresinNumaSocketByCoreId(mask, coreId); pthread_t currentThread = pthread_self(); // The following call forces migration of the thread if it is @@ -155,7 +138,7 @@ bool Affinity::SetNodeAffinity(int nodeId) bool result = true; cpu_set_t mask; CPU_ZERO(&mask); - GetGlobalConfiguration().SetMaskToAllCoresinNumaSocket2(mask, nodeId); + GetGlobalConfiguration().SetMaskToAllCoresinNumaSocketByNodeId(mask, nodeId); pthread_t currentThread = pthread_self(); // The following call forces migration of the thread if it is incorrectly placed @@ -173,10 +156,10 @@ bool Affinity::SetNodeAffinity(int nodeId) return result; } -static const char* AFFINITY_FILL_SOCKET_FIRST_STR = "fill-socket-first"; -static const char* AFFINITY_EQUAL_PER_SOCKET_STR = "equal-per-socket"; -static const char* AFFINITY_FILL_PHYSICAL_FIRST_STR = "fill-physical-first"; -static const char* AFFINITY_NONE_STR = "none"; +static const char* const AFFINITY_FILL_SOCKET_FIRST_STR = "fill-socket-first"; +static const char* const AFFINITY_EQUAL_PER_SOCKET_STR = "equal-per-socket"; +static const char* const AFFINITY_FILL_PHYSICAL_FIRST_STR = "fill-physical-first"; +static const char* const AFFINITY_NONE_STR = "none"; extern AffinityMode AffinityModeFromString(const char* affinityModeStr) { @@ -212,4 +195,12 @@ extern const char* AffinityModeToString(AffinityMode affinityMode) return "N/A"; } } + +extern bool ValidateAffinityMode(const char* affinityModeStr) +{ + if (AffinityModeFromString(affinityModeStr) == AffinityMode::AFFINITY_INVALID) { + return false; + } + return true; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.h b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.h index 7f060a01e..7ed5ec24f 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.h +++ b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.h @@ -25,7 +25,7 @@ #ifndef AFFINITY_H #define AFFINITY_H -#include +#include #include "type_formatter.h" namespace MOT { @@ -81,10 +81,10 @@ enum class AffinityMode { (((affinity) != MOT::AffinityMode::AFFINITY_NONE) && ((affinity) != MOT::AffinityMode::AFFINITY_INVALID)) /** @define Constant denoting an invalid CPU identifier. */ -#define INVALID_CPU_ID ((uint32_t)-1) +#define INVALID_CPU_ID (-1) /** @define Constant denoting an invalid NUMA node identifier. */ -#define INVALID_NODE_ID ((uint32_t)-1) +#define INVALID_NODE_ID (-1) /** * @class Affinity @@ -113,14 +113,14 @@ public: * @param threadId The logical identifier of the thread. * @return The CPU identifier, or @ref INVALID_CPU_ID in case of failure. */ - uint32_t GetAffineProcessor(uint64_t threadId) const; + int GetAffineProcessor(uint64_t threadId) const; /** * @brief Retrieves the identifier of the NUMA node affined to a given thread. * @param threadId The logical identifier of the thread. * @return The NUMA node identifier, or @ref INVALID_NODE_ID in case of failure. */ - uint32_t GetAffineNuma(uint64_t threadId) const; + int GetAffineNuma(uint64_t threadId) const; /** * @brief Sets the CPU affinity of a thread. @@ -128,7 +128,7 @@ public: * @param[out,opt] threadCore The resulting core identifier. * @return True if operation succeeded, otherwise false. */ - bool SetAffinity(uint64_t threadId, uint32_t* threadCore = nullptr) const; + bool SetAffinity(uint64_t threadId, int* threadCore = nullptr) const; /** * @brief Sets the CPU affinity of a thread to the specified NUMA node. @@ -188,6 +188,13 @@ extern AffinityMode AffinityModeFromString(const char* affinityModeStr); */ extern const char* AffinityModeToString(AffinityMode affinityMode); +/** + * @brief Validates the affinity mode string value. + * @param affinityModeStr The affinity string. + * @return True if affinity mode is valid, otherwise false. + */ +extern bool ValidateAffinityMode(const char* affinityModeStr); + /** * @class TypeFormatter * @brief Specialization of TypeFormatter with [ T = AffinityMode ]. diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/cycles.cpp b/src/gausskernel/storage/mot/core/infra/synchronization/cycles.cpp index 0edd6c938..b81907182 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/cycles.cpp +++ b/src/gausskernel/storage/mot/core/infra/synchronization/cycles.cpp @@ -26,7 +26,7 @@ #include "cycles.h" #include -#include +#include #include "utilities.h" diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/cycles.h b/src/gausskernel/storage/mot/core/infra/synchronization/cycles.h index 559ce1de6..89213af13 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/cycles.h +++ b/src/gausskernel/storage/mot/core/infra/synchronization/cycles.h @@ -26,7 +26,7 @@ #ifndef MOT_CPU_CYCLE_LEVEL_TIME_H #define MOT_CPU_CYCLE_LEVEL_TIME_H -#include +#include namespace MOT { /** diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/mot_atomic_ops.h b/src/gausskernel/storage/mot/core/infra/synchronization/mot_atomic_ops.h index 2c57cbf35..5b725e86c 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/mot_atomic_ops.h +++ b/src/gausskernel/storage/mot/core/infra/synchronization/mot_atomic_ops.h @@ -25,11 +25,18 @@ #ifndef MM_ATOMIC_OPS_H #define MM_ATOMIC_OPS_H -/** @define COMPILER_BARRIER A macro for generating a compiler barrier. */ +/** @define COMPILER_BARRIER A macro for generating a compiler barrier (same as defined in barrier.h). */ #if defined(__x86_64__) || defined(__x86__) -#define COMPILER_BARRIER asm volatile("" : : : "memory"); +#define COMPILER_BARRIER asm volatile("" : : : "memory") +#define MEMORY_BARRIER() asm volatile("lock; addl $0,0(%%rsp)" : : : "memory") +#define READ_BARRIER() COMPILER_BARRIER +#define WRITE_BARRIER() COMPILER_BARRIER #else -#define COMPILER_BARRIER __sync_synchronize(); +#define COMPILER_BARRIER __sync_synchronize() +#define MEMORY_BARRIER_DSB(opt) __asm__ __volatile__("DMB " #opt::: "memory") +#define MEMORY_BARRIER() MEMORY_BARRIER_DSB(ish) +#define READ_BARRIER() MEMORY_BARRIER_DSB(ishld) +#define WRITE_BARRIER() MEMORY_BARRIER_DSB(ishst) #endif /** @define PAUSE A macro for memory pause. */ diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/rwspinlock.h b/src/gausskernel/storage/mot/core/infra/synchronization/rwspinlock.h index 9e7c2a98b..f8d251b05 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/rwspinlock.h +++ b/src/gausskernel/storage/mot/core/infra/synchronization/rwspinlock.h @@ -129,7 +129,7 @@ static __LOCK_INLINE int spinlock_trylock(spinlock_t* lock) * @param lock The spin-lock to query its state. * @return Non-zero value if the spin-lock is locked, otherwise zero. */ -static __LOCK_INLINE int spinlock_is_locked(spinlock_t* lock) +static __LOCK_INLINE bool spinlock_is_locked(spinlock_t* lock) { return (*lock == MEBUSY); } diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/spin_lock.h b/src/gausskernel/storage/mot/core/infra/synchronization/spin_lock.h index fd19277c0..f5c3e5329 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/spin_lock.h +++ b/src/gausskernel/storage/mot/core/infra/synchronization/spin_lock.h @@ -100,8 +100,8 @@ private: }; } // namespace MOT -#endif /* SPIN_LOCK_H */ - #if defined(MOT_SPINLOCK_INLINE) #include "spin_lock.cpp" #endif + +#endif /* SPIN_LOCK_H */ diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/thread_utils.h b/src/gausskernel/storage/mot/core/infra/synchronization/thread_utils.h new file mode 100644 index 000000000..9b163795a --- /dev/null +++ b/src/gausskernel/storage/mot/core/infra/synchronization/thread_utils.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * thread_utils.h + * Utility classes for managing threads (notifier, context, etc). + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/infra/synchronization/thread_utils.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef MOT_THREAD_UTILS_H +#define MOT_THREAD_UTILS_H + +#include +#include +#include + +namespace MOT { +typedef bool (*ThreadNotifierWaitPredicate)(void* obj); + +class ThreadNotifier { +public: + enum ThreadState : uint32_t { TERMINATE = 0, SLEEP = 1, ACTIVE = 2 }; + + ThreadNotifier(ThreadState state = ThreadState::SLEEP) : m_state(state) + {} + + ~ThreadNotifier() + {} + + void SetState(ThreadState state) + { + std::unique_lock lock(m_mutex); + m_state = state; + } + + ThreadState GetState() const + { + return m_state; + } + + void Notify(ThreadState state) + { + SetState(state); + m_cv.notify_all(); + } + + ThreadState Wait(ThreadNotifierWaitPredicate pred, void* obj) + { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [pred, obj] { return pred(obj); }); + return m_state; + } + +private: + volatile ThreadState m_state; + std::mutex m_mutex; + std::condition_variable m_cv; +}; + +class ThreadContext { +public: + ThreadContext() : m_ready(false), m_error(false) + {} + + virtual ~ThreadContext() + { + m_ready.store(false); + m_error = false; + } + + void SetReady() + { + m_ready.store(true); + } + + bool IsReady() const + { + return m_ready.load(); + } + + virtual void SetError() + { + m_error.store(true); + } + + virtual bool IsError() + { + return m_error.load(); + } + + static constexpr uint32_t THREAD_START_WAIT_US = 1000; + static constexpr uint32_t THREAD_START_TIMEOUT_US = 1000 * 1000 * 60; // 60 sec + static constexpr uint32_t THREAD_SLEEP_TIME_US = 100; + static constexpr uint32_t THREAD_NAME_LEN = 64; + +private: + std::atomic m_ready; + std::atomic m_error; +}; + +inline bool WaitForThreadStart(ThreadContext* thrdContext) +{ + uint32_t rounds = 0; + uint32_t maxRounds = ThreadContext::THREAD_START_TIMEOUT_US / ThreadContext::THREAD_START_WAIT_US; + while (true) { + if (thrdContext->IsReady()) { + return true; + } + if (thrdContext->IsError()) { + return false; + } + if (rounds++ > maxRounds) { + break; + } + (void)usleep(ThreadContext::THREAD_START_WAIT_US); + } + return false; +} +} // namespace MOT + +#endif /* MOT_THREAD_UTILS_H */ diff --git a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.cpp b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.cpp index 51fd9b4ed..372e535b6 100644 --- a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.cpp +++ b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.cpp @@ -24,85 +24,77 @@ #include "mm_gc_manager.h" #include "mot_configuration.h" +#include "mot_engine.h" namespace MOT { IMPLEMENT_CLASS_LOGGER(GcManager, GC); class Table; -/** @var Global epoch, updated every barrier */ -volatile GcEpochType g_gcGlobalEpoch = 1; -/** @var Current lowest epoch among all active GC Managers */ -volatile GcEpochType g_gcActiveEpoch = 1; - /** @var Lock to sync GC Manager's Limbo groups access by other threads */ GcLock g_gcGlobalEpochLock; GcManager* GcManager::allGcManagers = nullptr; -inline GcManager::GcManager(GC_TYPE purpose, int getThreadId, int rcuMaxFreeCount) - : m_rcuFreeCount(rcuMaxFreeCount), m_tid(getThreadId), m_purpose(purpose) +static GcQueueEntry gcQueuesParams[static_cast(GC_QUEUE_TYPE::GC_QUEUES)] = { + {GC_QUEUE_TYPE::DELETE_QUEUE, 1024, 8388608, 500}, + {GC_QUEUE_TYPE::VERSION_QUEUE, 102400, 8388608, 1000}, + {GC_QUEUE_TYPE::UPDATE_COLUMN_QUEUE, 1, 8388608, 100000}, + {GC_QUEUE_TYPE::GENERIC_QUEUE, 102400, 8388608, 1000}}; + +uint64_t GetGlobalEpoch() +{ + return MOTEngine::GetInstance()->GetGcEpoch(); +} + +uint64_t GetCurrentSnapshotCSN() +{ + return MOTEngine::GetInstance()->GetCurrentCSN(); +} + +inline GcManager::GcManager(GC_TYPE purpose, int threadId, bool global /* = false */) + : m_gcEpoch(0), + m_limboGroupPool(nullptr), + m_next(nullptr), + m_limboGroupAllocations(0), + m_tid((uint16_t)threadId), + m_purpose(purpose), + m_global(global) {} bool GcManager::Initialize() { bool result = true; - - if (m_purpose == GC_MAIN) { - m_limboGroupPool = ObjAllocInterface::GetObjPool(sizeof(LimboGroup), true); - if (!m_limboGroupPool) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Initialize ObjectPool", - "Failed to allocate LimboGroup pool for thread %d", - GetThreadId()); - return false; - } - -#ifdef MOT_DEBUG - PoolStatsSt stats; - errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); - securec_check(erc, "\0", "\0"); - stats.m_type = PoolStatsT::POOL_STATS_ALL; - m_limboGroupPool->GetStats(stats); - m_limboGroupPool->PrintStats(stats, "GC_MANAGER", LogLevel::LL_INFO); -#endif - - LimboGroup* limboGroup = CreateNewLimboGroup(); - if (limboGroup == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Create GC Context", - "Failed to allocate %u bytes for limbo group", - (unsigned)sizeof(LimboGroup)); - result = false; - } else { - m_limboHead = m_limboTail = limboGroup; - m_limboGroupAllocations = 1; - } - } else { - m_limboHead = m_limboTail = nullptr; - m_limboGroupAllocations = 0; + m_limboGroupPool = ObjAllocInterface::GetObjPool(sizeof(LimboGroup), !m_global); + if (!m_limboGroupPool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Initialize ObjectPool", "Failed to allocate LimboGroup pool for thread %d", GetThreadId()); + return false; } - if (result) { - m_totalLimboInuseElements = 0; - m_totalLimboSizeInBytes = 0; - m_totalLimboReclaimedSizeInBytes = 0; - m_totalLimboRetiredSizeInBytes = 0; - m_totalLimboSizeInBytesByCleanIndex = 0; + uint32_t queueSize = static_cast(GC_QUEUE_TYPE::GC_QUEUES); + m_GcQueues.reserve(queueSize); + for (uint32_t q = 0; q < queueSize; q++) { + m_GcQueues.push_back(GcQueue(gcQueuesParams[q])); + result = m_GcQueues[q].Initialize(this); + if (result == false) { + return result; + } + } + + if (!m_reservedManager.initialize()) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create GC Context", "Failed to create GcQueueReservedMemory"); + result = false; } return result; } -GcManager* GcManager::Make(GC_TYPE purpose, int threadId, int rcuMaxFreeCount) +GcManager* GcManager::Make(GC_TYPE purpose, int threadId, bool global) { + static bool once = false; GcManager* gc = nullptr; - -#ifdef MEM_SESSION_ACTIVE - void* gcBuffer = MemSessionAlloc(sizeof(GcManager)); -#else - void* gcBuffer = malloc(sizeof(GcManager)); -#endif + void* gcBuffer = MemAlloc(sizeof(GcManager), global); if (gcBuffer == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, @@ -113,29 +105,24 @@ GcManager* GcManager::Make(GC_TYPE purpose, int threadId, int rcuMaxFreeCount) } else { errno_t erc = memset_s(gcBuffer, sizeof(GcManager), 0, sizeof(GcManager)); securec_check(erc, "\0", "\0"); - gc = new (gcBuffer) GcManager(purpose, threadId, rcuMaxFreeCount); + gc = new (gcBuffer) GcManager(purpose, threadId, global); if (!gc->Initialize()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Create GC Context", "Failed to initialize GC context object"); gc->~GcManager(); - -#ifdef MEM_SESSION_ACTIVE - MemSessionFree(gcBuffer); -#else - free(gcBuffer); -#endif + gc = nullptr; + MemFree(gcBuffer, global); } else { - MOTConfiguration& cfg = GetGlobalConfiguration(); - gc->m_isGcEnabled = cfg.m_gcEnable; - gc->m_limboSizeLimit = (uint32_t)cfg.m_gcReclaimThresholdBytes; - gc->m_limboSizeLimitHigh = (uint32_t)cfg.m_gcHighReclaimThresholdBytes; - gc->m_rcuFreeCount = cfg.m_gcReclaimBatchSize; - if (threadId == 0) { - MOT_LOG_INFO( - "GC PARAMS: isGcEnabled = %s, limboSizeLimit = %d, limboSizeLimitHigh = %d, rcuFreeCount = %d", - gc->m_isGcEnabled ? "true" : "false", - gc->m_limboSizeLimit, - gc->m_limboSizeLimitHigh, - gc->m_rcuFreeCount); + if (!once) { + MOT_LOG_INFO("GC PARAMS: "); + for (uint32_t queue = 0; queue < gc->m_GcQueues.size(); ++queue) { + MOT_LOG_INFO("QUEUE%u:%s SizeLimit = %uKB, SizeLimitHigh = %uKB, Count = %u", + queue, + GcQueue::enGcQueue[queue], + gcQueuesParams[queue].m_limboSizeLimit / 1024, + gcQueuesParams[queue].m_limboSizeLimitHigh / 1024, + gcQueuesParams[queue].m_rcuFreeCount) + } + once = true; } } } @@ -143,157 +130,193 @@ GcManager* GcManager::Make(GC_TYPE purpose, int threadId, int rcuMaxFreeCount) return gc; } -void GcManager::HardQuiesce(uint32_t numOfElementsToClean) +void GcManager::CleanIndexItems(uint32_t indexId, GC_OPERATION_TYPE oper) { - LimboGroup* emptyHead = nullptr; - LimboGroup* emptyTail = nullptr; - unsigned count = numOfElementsToClean; - unsigned locCount = 0; - - // Update values from cleanIndexItemPerGroup flow - if (m_totalLimboSizeInBytesByCleanIndex) { - m_totalLimboSizeInBytes -= m_totalLimboSizeInBytesByCleanIndex; - m_totalLimboReclaimedSizeInBytes += m_totalLimboSizeInBytesByCleanIndex; - m_totalLimboSizeInBytesByCleanIndex = 0; - } - - GcEpochType epochBound = g_gcActiveEpoch - 1; - if (m_limboHead->m_head == m_limboHead->m_tail || GcSignedEpochType(epochBound - m_limboHead->FirstEpoch()) < 0) - goto done; - - // clean [limbo_head_, limbo_tail_] - while (count) { - locCount = m_limboHead->CleanUntil(*this, epochBound, count); - m_totalLimboInuseElements -= (count - locCount); - count = locCount; - if (m_limboHead->m_head != m_limboHead->m_tail) { - break; - } - if (emptyHead == nullptr) { - emptyHead = m_limboHead; - } - emptyTail = m_limboHead; - if (m_limboHead == m_limboTail) { - m_limboHead = m_limboTail = emptyHead; - goto done; - } - m_limboHead = m_limboHead->m_next; - } - // hook empties after limbo_tail_ - if (emptyHead != nullptr) { - emptyTail->m_next = m_limboTail->m_next; - m_limboTail->m_next = emptyHead; - } - -done: - if (!count) { - m_performGcEpoch = epochBound; // do GC again immediately - } else { - m_performGcEpoch = epochBound + 1; - } - - MOT_LOG_DEBUG("threadId = %d cleaned items = %d\n", m_tid, m_rcuFreeCount - count); -} - -inline unsigned LimboGroup::CleanUntil(GcManager& ti, GcEpochType epochBound, unsigned count) -{ - EpochType epoch = 0; - uint32_t size = 0; - while (m_head != m_tail) { - if (m_elements[m_head].m_objectPtr) { - size = m_elements[m_head].m_cb(m_elements[m_head].m_objectPtr, m_elements[m_head].m_objectPool, false); - MemoryStatisticsProvider::m_provider->AddGCReclaimedBytes(size); - ti.m_totalLimboSizeInBytes -= size; - ti.m_totalLimboReclaimedSizeInBytes += size; // stats - --count; - if (!count) { - m_elements[m_head].m_objectPtr = nullptr; - m_elements[m_head].m_epoch = epoch; - break; - } - } else { - epoch = m_elements[m_head].m_epoch; - if (SignedEpochType(epochBound - epoch) < 0) { - break; - } - } - ++m_head; - } - if (m_head == m_tail) { - m_head = m_tail = 0; - } - return count; -} - -inline unsigned LimboGroup::CleanIndexItemPerGroup(GcManager& ti, uint32_t indexId, bool dropIndex) -{ - unsigned gHead = m_head; - unsigned gTail = m_tail; - uint32_t size = 0; - uint32_t itemCleaned = 0; - - while (gHead != gTail) { - if (m_elements[gHead].m_objectPtr != nullptr && m_elements[gHead].m_indexId == indexId && - m_elements[gHead].m_cb != ti.NullDtor) { - size = m_elements[gHead].m_cb(m_elements[gHead].m_objectPtr, m_elements[gHead].m_objectPool, dropIndex); - MemoryStatisticsProvider::m_provider->AddGCReclaimedBytes(size); - ti.m_totalLimboSizeInBytesByCleanIndex += size; - m_elements[gHead].m_cb = ti.NullDtor; - itemCleaned++; - } - ++gHead; - } - return itemCleaned; -} - -void GcManager::CleanIndexItems(uint32_t indexId, bool dropIndex) -{ - if (m_isGcEnabled == false) { - return; - } uint32_t counter = 0; + m_managerLock.lock(); - LimboGroup* head = m_limboHead; - LimboGroup* tail = m_limboTail; - while (head != nullptr) { - counter += head->CleanIndexItemPerGroup(*this, indexId, dropIndex); - head = head->m_next; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + counter += m_GcQueues[queue].CleanIndexItems(indexId, oper); } m_managerLock.unlock(); + if (counter) { - MOT_LOG_INFO("Entity:%s threadId = %d cleaned from index id = %u items = %u\n", - enGcTypes[m_purpose], + uint64_t items = GetTotalLimboInuseElements(); + MOT_LOG_INFO("Entity:%s threadId = %d cleaned from index id = %d items = %u inuseItems = %lu", + enGcTypes[static_cast(m_purpose)], m_tid, indexId, - counter); + counter, + items); } } -bool GcManager::RefillLimboGroup() +bool GcManager::ReserveGCMemory(uint32_t elements) { - if (!m_limboTail->m_next) { + bool res = true; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + if (m_GcQueues[queue].GetFreeAllocations() < elements) { + res = m_GcQueues[queue].ReserveGCMemory(elements); + if (res == false) { + return false; + } + } + } + return res; +} + +bool GcManager::ReserveGCMemoryPerQueue(GC_QUEUE_TYPE queueId, uint32_t elements, bool reserveGroups) +{ + if (elements == 0) { + return true; + } + + MOT_ASSERT(queueId < GC_QUEUE_TYPE::GC_QUEUES); + + return m_GcQueues[static_cast(queueId)].ReserveGCMemory(elements, reserveGroups); +} + +bool GcManager::ReserveGCRollbackMemory(uint32_t elements) +{ + LimboGroup* list = nullptr; + if (elements == 0) { + return true; + } + + uint32_t limboGroups = static_cast(std::ceil(double(elements) / LimboGroup::CAPACITY)); + list = AllocLimboGroups(limboGroups); + if (list == nullptr) { + return false; + } + m_reservedManager.ReserveGCMemory(list, elements); + return true; +} + +LimboGroup* GcManager::AllocLimboGroups(uint32_t limboGroups) +{ + LimboGroup* currentTail = nullptr; + LimboGroup* currentHead = nullptr; + for (uint32_t group = 0; group < limboGroups; group++) { LimboGroup* limboGroup = CreateNewLimboGroup(); - if (limboGroup == nullptr) { + if (limboGroup != nullptr) { + if (!currentTail) { + currentTail = limboGroup; + currentHead = currentTail; + } else { + limboGroup->m_next = currentHead; + currentHead = limboGroup; + } + } else { + // Release memory + while (currentHead) { + LimboGroup* tmp = currentHead; + currentHead = currentHead->m_next; + DestroyLimboGroup(tmp); + } MOT_REPORT_ERROR(MOT_ERROR_OOM, - "GC Refill Limbo Group", + "GC Reserve Limbo Group", "Failed to allocate %u bytes for limbo group", (unsigned)sizeof(LimboGroup)); - return false; + return nullptr; } - m_limboTail->m_next = limboGroup; - m_limboGroupAllocations++; } - m_limboTail = m_limboTail->m_next; - MOT_ASSERT(m_limboTail->m_head == 0 && m_limboTail->m_tail == 0); - return true; + + return currentHead; +} + +void GcManager::RunQuicese() +{ + bool isBarrierReached = false; + GcEpochType performGCEpoch = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + isBarrierReached = false; + m_gcEpoch = GetGlobalEpoch(); + bool isThresholdReached = m_GcQueues[queue].IsThresholdReached(); + if (isThresholdReached) { + if (!m_isThresholdReached) { + m_GcQueues[queue].SetPerformGcEpoch(g_gcActiveEpoch); + } else { + m_GcQueues[queue].SetPerformGcEpoch(performGCEpoch); + } + + if (!m_isThresholdReached) { + if (m_GcQueues[queue].GetPerformGcEpoch() <= m_GcQueues[queue].FirstEpoch()) { + MOT_LOG_DEBUG("Entity:%s THD_ID:%d Increase Epoch", GcQueue::enGcQueue[queue], m_tid); + SetActiveEpoch(isBarrierReached, GetThreadId()); + m_GcQueues[queue].SetPerformGcEpoch(g_gcActiveEpoch); + performGCEpoch = m_GcQueues[queue].GetPerformGcEpoch(); + m_isThresholdReached = true; + } + } + + m_GcQueues[queue].RunQuiesce(); + } + } + GcMaintenance(); + m_isThresholdReached = false; + m_gcEpoch = 0; +} + +void GcManager::GcCleanAll() +{ + m_gcEpoch = GetGlobalEpoch(); + bool isBarrierReached = false; + MOT_LOG_DEBUG("Entity:%s THD_ID:%d start closing session .... #allocations = %d", + enGcTypes[static_cast(m_purpose)], + m_tid, + m_limboGroupAllocations); + + uint64_t inuseElements = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + inuseElements += m_GcQueues[queue].m_stats.m_totalLimboInuseElements; + while (m_GcQueues[queue].m_stats.m_totalLimboInuseElements > 0) { + // Read Latest snapshot + m_gcEpoch = GetGlobalEpoch(); + m_managerLock.lock(); + // Increase the global epoch to insure all elements are from a lower epoch + SetActiveEpoch(isBarrierReached, GetThreadId()); + if ((queue == static_cast(GC_QUEUE_TYPE::GENERIC_QUEUE) or + MOTEngine::GetInstance()->IsRecovering()) and + isBarrierReached) { + m_GcQueues[queue].SetPerformGcEpoch(g_gcActiveEpoch + 1); + } else { + m_GcQueues[queue].SetPerformGcEpoch(g_gcActiveEpoch); + } + uint64_t cleaned = m_GcQueues[queue].m_stats.m_totalLimboInuseElements; + m_GcQueues[queue].HardQuiesce(m_GcQueues[queue].m_stats.m_totalLimboInuseElements); + if (cleaned != m_GcQueues[queue].m_stats.m_totalLimboInuseElements) { + MOT_LOG_DEBUG("Entity:%s THD_ID:%d cleaned %lu from queue %s elements left: %lu elements", + enGcTypes[static_cast(m_purpose)], + m_tid, + cleaned - m_GcQueues[queue].m_stats.m_totalLimboInuseElements, + GcQueue::enGcQueue[queue], + m_GcQueues[queue].m_stats.m_totalLimboInuseElements); + } + // Reset local epoch + m_gcEpoch = 0; + m_GcQueues[queue].RegisterDeletedSentinels(); + m_managerLock.unlock(); + // every 20ms + break symmetry + if (m_GcQueues[queue].m_stats.m_totalLimboInuseElements > 0) { + (void)usleep(GC_CLEAN_SLEEP + m_tid * GC_CLEAN_SESSION_SLEEP); + } + } + m_managerLock.lock(); + m_GcQueues[queue].Maintenance(); + m_managerLock.unlock(); + } + + m_gcEpoch = 0; + MOT_LOG_DEBUG("Entity:%s THD_ID:%d closed session cleaned %lu elements from limbo! #allocations = %u", + enGcTypes[static_cast(m_purpose)], + m_tid, + inuseElements, + m_limboGroupAllocations); } void GcManager::RemoveFromGcList(GcManager* n) { // When node to be deleted is head node - if (m_isGcEnabled == false) { - return; - } MOT_ASSERT(n != nullptr); g_gcGlobalEpochLock.lock(); @@ -315,7 +338,6 @@ void GcManager::RemoveFromGcList(GcManager* n) // Check if node really exists in Linked List if (prev->m_next == nullptr) { - printf("\nGiven node is not present in Linked List\n"); g_gcGlobalEpochLock.unlock(); return; } @@ -328,16 +350,22 @@ void GcManager::RemoveFromGcList(GcManager* n) void GcManager::ReportGcStats() { - MOT_LOG_INFO("----------GC Thd:%d-----------\n", m_tid); - MOT_LOG_INFO("total_limbo_inuse_elements = %d\n", m_totalLimboInuseElements); - MOT_LOG_INFO("limbo_group_allocations = %d\n", m_limboGroupAllocations); - MOT_LOG_INFO("total_limbo_reclaimed_size_in_bytes = %lld in MB = %lf \n", - m_totalLimboReclaimedSizeInBytes, - double((double)m_totalLimboReclaimedSizeInBytes / ONE_MB)); - MOT_LOG_INFO("total_limbo_retired_size_in_bytes = %lld in MB = %lf \n", - m_totalLimboRetiredSizeInBytes, - double((double)m_totalLimboRetiredSizeInBytes / ONE_MB)); - MOT_LOG_INFO("------------------------------\n"); + MOT_LOG_INFO("----------GC Thd:%d m_gcEpoch = %lu-----------", m_tid, m_gcEpoch); + MOT_LOG_INFO("total_limbo_inuse_elements = %lu", GetTotalLimboInuseElements()); + MOT_LOG_INFO("limbo_group_allocations = %u", GetLimboGroupAllocations()); + MOT_LOG_INFO("total_limbo_reclaimed_size_in_bytes = %lu in MB = %lf", + GetTotalLimboReclaimedSizeInBytes(), + double((double)GetTotalLimboReclaimedSizeInBytes() / ONE_MB)); + MOT_LOG_INFO("total_limbo_retired_size_in_bytes = %lu in MB = %lf", + GetTotalLimboRetiredSizeInBytes(), + double((double)GetTotalLimboRetiredSizeInBytes() / ONE_MB)); + PoolStatsSt stats; + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + + m_limboGroupPool->GetStats(stats); + m_limboGroupPool->PrintStats(stats, "Limbo Group Pool", LogLevel::LL_INFO); } void GcManager::ReportGcAll() @@ -346,4 +374,19 @@ void GcManager::ReportGcAll() ti->ReportGcStats(); } } + +void GcManager::GcReinitEpoch() +{ + m_gcEpoch = 0; + COMPILER_BARRIER; + if (MOTEngine::GetInstance()->IsRecovering()) { + while (MOTEngine::GetInstance()->IsRecoveryPerformingCleanup()) { + PAUSE; + } + } + if (m_isTxnSnapshotTaken) { + m_gcEpoch = GetGlobalEpoch(); + } +} + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.h b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.h index fc306837c..157173c9d 100644 --- a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.h +++ b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_manager.h @@ -30,118 +30,19 @@ #include "utilities.h" #include "memory_statistics.h" #include "mm_session_api.h" +#include "mm_gc_queue.h" +#include #include "object_pool.h" namespace MOT { class GcManager; -typedef uint64_t GcEpochType; -typedef int64_t GcSignedEpochType; -typedef MOT::spin_lock GcLock; - -/** @def GC callback function signature */ -typedef uint32_t (*DestroyValueCbFunc)(void*, void*, bool); -extern volatile GcEpochType g_gcGlobalEpoch; extern volatile GcEpochType g_gcActiveEpoch; extern GcLock g_gcGlobalEpochLock; -inline uint64_t GetGlobalEpoch() -{ - return g_gcGlobalEpoch; -} +uint64_t GetGlobalEpoch(); -/** - * @struct LimboGroup - * @brief Contains capacity elements and handle push/pop operations - */ -struct LimboGroup { - typedef GcEpochType EpochType; - typedef GcSignedEpochType SignedEpochType; - - /** - * @struct LimboElement - * @brief Contains all the necessary members to reclaim the object - */ - struct LimboElement { - void* m_objectPtr; - - void* m_objectPool; - - DestroyValueCbFunc m_cb; - - EpochType m_epoch; - - uint32_t m_indexId; - }; - - static_assert(sizeof(LimboElement) > (sizeof(LimboGroup*) + sizeof(EpochType) + 2 * sizeof(unsigned)), - "ERROR:Please Modify group size!"); - - static constexpr uint32_t GROUP_SIZE = 8 * KILO_BYTE - sizeof(LimboElement); - - /** Calculate the capacity of the group for minimal memory overhead (S-1/S) - * Where a single element is at least larger from the rest of the Struct members - */ - static constexpr uint32_t CAPACITY = GROUP_SIZE / sizeof(LimboElement); - - unsigned m_head; - - unsigned m_tail; - - EpochType m_epoch; - - LimboGroup* m_next; - - LimboElement m_elements[CAPACITY]; - - LimboGroup() : m_head(0), m_tail(0), m_next() - {} - - EpochType FirstEpoch() const - { - MOT_ASSERT(m_head != m_tail); - return m_elements[m_head].m_epoch; - } - - /** - * @brief Push an element to the list, if the epoch is new create a new dummy element - * @param indexId Element index-id - * @param objectPtr Pointer to reclaim - * @param objectPool Memory pool (optional) - * @param cb Callback function - * @param epoch Recorded epoch - */ - void PushBack(uint32_t indexId, void* objectPtr, void* objectPool, DestroyValueCbFunc cb, GcEpochType epoch) - { - MOT_ASSERT(m_tail + 2 <= CAPACITY); - if (m_head == m_tail || m_epoch != epoch) { - m_elements[m_tail].m_objectPtr = nullptr; - m_elements[m_tail].m_epoch = epoch; - m_epoch = epoch; - ++m_tail; - } - m_elements[m_tail].m_indexId = indexId; - m_elements[m_tail].m_objectPtr = objectPtr; - m_elements[m_tail].m_objectPool = objectPool; - m_elements[m_tail].m_cb = cb; - ++m_tail; - } - - /** @brief Clean all element until epochBound - * @param ti GcManager object to clean - * @param epochBound Epoch boundary to limit cleanup - * @param count Max items to clean - */ - inline unsigned CleanUntil(GcManager& ti, GcEpochType epochBound, unsigned count); - - /** - * @brief Clean All elements from the current GC manager tagged with index_id - * @param ti GcManager object to clean - * @param indexId Index Identifier to clean - * @return Number of elements cleaned - */ - inline unsigned CleanIndexItemPerGroup(GcManager& ti, uint32_t indexId, bool dropIndex); -}; +uint64_t GetCurrentSnapshotCSN(); static const char* const enGcTypes[] = { stringify(GC_MAIN), stringify(GC_INDEX), stringify(GC_LOG), stringify(GC_CHECKPOINT)}; @@ -154,18 +55,11 @@ class alignas(64) GcManager { public: ~GcManager() { - LimboGroup* temp = m_limboHead; - LimboGroup* next = nullptr; - while (temp) { - next = temp->m_next; - DestroyLimboGroup(temp); - temp = next; - } - if (m_limboGroupPool) { ObjAllocInterface::FreeObjPool(&m_limboGroupPool); } m_limboGroupPool = nullptr; + m_next = nullptr; } GcManager(const GcManager&) = delete; @@ -173,7 +67,7 @@ public: GcManager& operator=(const GcManager&) = delete; /** @var GC managers types */ - enum GC_TYPE : uint8_t { GC_MAIN, GC_INDEX, GC_LOG, GC_CHECKPOINT }; + enum class GC_TYPE : uint8_t { GC_MAIN, GC_INDEX, GC_RECOVERY, GC_CHECKPOINT }; /** @var List of all GC Managers */ static GcManager* allGcManagers; @@ -193,22 +87,64 @@ public: * @param rcuMaxFreeCount How many objects to reclaim * @return Pointer to instance of a GC manager */ - static GcManager* Make(GC_TYPE purpose, int threadId, int rcuMaxFreeCount = MASSTREE_OBJECT_COUNT_PER_CLEANUP); - + static GcManager* Make(GC_TYPE purpose, int threadId, bool global = false); /** * @brief Add Current manager to the global list */ inline void AddToGcList() { - if (m_isGcEnabled == false) { - return; - } g_gcGlobalEpochLock.lock(); m_next = allGcManagers; allGcManagers = this; g_gcGlobalEpochLock.unlock(); } + /** @brief Reserver GC Memory */ + bool ReserveGCMemory(uint32_t elements); + + /** @brief Reserver GC Memory for specific queue */ + bool ReserveGCMemoryPerQueue(GC_QUEUE_TYPE queueId, uint32_t elements, bool reserveGroups = false); + + /** @brief Reserver GC Memory for transaction rollback */ + bool ReserveGCRollbackMemory(uint32_t elements); + + /** @brief allocate multiple limbo groups */ + LimboGroup* AllocLimboGroups(uint32_t limboGroups); + + /** @brief allocate limbo group */ + LimboGroup* CreateNewLimboGroup() + { + LimboGroup* group = m_limboGroupPool->Alloc(); + if (group == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Create LimboGroup", "Failed to create new LimboGroup in Thread %d", GetThreadId()); + } else { + m_limboGroupAllocations++; + } + return group; + } + + /** @brief allocate limbo group from reserved pool */ + LimboGroup* AllocLimboGroupFromReserved() + { + return m_reservedManager.AllocLimboGroup(); + } + + /** @brief release limbo group */ + void DestroyLimboGroup(LimboGroup* obj) + { + m_limboGroupPool->Release(obj); + m_limboGroupAllocations--; + } + + void ClearLimboGroupCache() + { + if (m_global) { + m_limboGroupPool->ClearThreadCache(); + } else { + m_limboGroupPool->ClearFreeCache(); + } + } /** @brief remove manager from global list */ void RemoveFromGcList(GcManager* ti); @@ -218,7 +154,12 @@ public: /** @brief Print report of all threads */ static void ReportGcAll(); - int GetThreadId() const + void SetThreadId(uint16_t threadId) + { + m_tid = threadId; + } + + uint16_t GetThreadId() const { return m_tid; } @@ -228,12 +169,12 @@ public: m_purpose = type; } - const char* GetGcTypeStr() + const char* GetGcTypeStr() const { - return enGcTypes[m_purpose]; + return enGcTypes[static_cast(m_purpose)]; } - GC_TYPE GetGcType() + GC_TYPE GetGcType() const { return m_purpose; } @@ -253,14 +194,69 @@ public: m_gcEpoch = 0; } - void GcStartTxn() + uint64_t GetCurrentEpoch() const { + return m_gcEpoch; + } - if (m_isGcEnabled == true and m_isTxnStarted == false) { - m_isTxnStarted = true; - if (m_gcEpoch != GetGlobalEpoch()) - m_gcEpoch = GetGlobalEpoch(); + bool IsGcSnapshotTaken() const + { + return m_isTxnSnapshotTaken; + } + + /** @brief Start GC session */ + RC GcStartTxn(uint64_t csn = 0) + { + RC rc = RC_OK; + + if (!m_isTxnSnapshotTaken) { + m_isTxnSnapshotTaken = true; + m_isGcSessionStarted = true; + if (csn) { + m_gcEpoch = csn; + } else { + // local guard the current epoch acquisition for correct minimum calculation + m_managerLock.lock(); + if (GetGcType() == GC_TYPE::GC_CHECKPOINT || GetGcType() == GC_TYPE::GC_RECOVERY) { + m_gcEpoch = GetGlobalEpoch(); + } else { + m_gcEpoch = GetCurrentSnapshotCSN(); + } + m_managerLock.unlock(); + } + bool res = ReserveGCMemory(LIMBO_GROUP_INIT_SIZE * LimboGroup::CAPACITY); + if (!res) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Transaction Execution", + "GC Failed to refill %d elements (while reallocating)", + LIMBO_GROUP_INIT_SIZE * LimboGroup::CAPACITY); + SetLastError(MOT_ERROR_OOM, MOT_SEVERITY_ERROR); + return RC::RC_MEMORY_ALLOCATION_ERROR; + } } + return rc; + } + + /** + * @brief Signal to the Gc manager that a transaction is re-started in terms of reclaimable memory usage + */ + void GcReinitEpoch(); + + /** + * @brief Signal to the Gc manger that a statement in a read-only transaction has ended. + * and remove yourself from the barrier + */ + void GcEndStatement() + { + m_isTxnSnapshotTaken = false; + m_gcEpoch = 0; + } + + void GcRecoveryEndTxn() + { + m_isTxnSnapshotTaken = false; + m_isGcSessionStarted = false; + m_gcEpoch = 0; } /** @@ -268,16 +264,23 @@ public: */ void GcEndTxn() { - if (m_isGcEnabled == false || m_isTxnStarted == false) { + if (!m_isGcSessionStarted) { return; } - // Always lock before quicese to allow drop-table/check-point operations + ValidateAllocations(); + // Always lock before quiesce to allow drop-table/check-point operations if (m_managerLock.try_lock()) { RunQuicese(); m_managerLock.unlock(); } + m_isTxnSnapshotTaken = false; + m_isGcSessionStarted = false; m_gcEpoch = 0; - m_isTxnStarted = false; +#ifdef GC_MASSTREE_DEBUG + m_mastreeElements = 0; +#endif + GcCleanRollbackMemory(); + ValidateAllocations(); } /** @@ -286,88 +289,36 @@ public: * 2. Perform reclamation if possible * 3. Check hard-limit */ - void RunQuicese() - { - // Update values from cleanIndexItemPerGroup flow - if (m_totalLimboSizeInBytesByCleanIndex) { - m_totalLimboSizeInBytes -= m_totalLimboSizeInBytesByCleanIndex; - m_totalLimboReclaimedSizeInBytes += m_totalLimboSizeInBytesByCleanIndex; - m_totalLimboSizeInBytesByCleanIndex = 0; - } - - // Increase Local epoch when the threashold is reached - if (m_totalLimboSizeInBytes > m_limboSizeLimit) { - m_gcEpoch++; - } - // if local epoch is greater then global set the global and calculate minimum - if (m_gcEpoch > g_gcGlobalEpoch) { - SetGlobalEpoch(m_gcEpoch); - } - // Perform reclamation if possible - Quiesce(); - - // If we still exceed the high size limit, (e.g. we had a major gc addition in this txn run), clean all elements - // (up to epoch limitation) - if (m_totalLimboSizeInBytes > m_limboSizeLimitHigh) { - HardQuiesce(m_totalLimboInuseElements); - ShrinkMem(); - } - } - - inline void Quiesce() - { - if (m_performGcEpoch != g_gcActiveEpoch) - HardQuiesce(m_rcuFreeCount); - } + void RunQuicese(); /** @brief Clean all object at the end of the session */ - inline void GcCleanAll() - { - if (m_isGcEnabled == false) { - return; - } - uint32_t inuseElements = m_totalLimboInuseElements; - // Increase the global epoch to insure all elements are from a lower epoch - while (m_totalLimboSizeInBytes > 0) { - SetGlobalEpoch(GetGlobalEpoch() + 1); - m_managerLock.lock(); - HardQuiesce(m_totalLimboInuseElements); - m_managerLock.unlock(); - // every 200ms - usleep(200 * 1000); - } - m_managerLock.lock(); - ShrinkMem(); - m_managerLock.unlock(); - MOT_LOG_DEBUG("Entity:%s THD_ID:%d closed session cleaned %d elements from limbo!\n", - enGcTypes[m_purpose], - m_tid, - inuseElements); - } + void GcCleanAll(); /** @brief Clean all object at the end of the session */ inline void GcCheckPointClean() { - if (m_isGcEnabled == false) { - return; - } - - // End CP Txn - m_gcEpoch = 0; - m_isTxnStarted = false; + uint64_t inuseElements = 0; + bool isBarrierReached = false; // Increase the global epoch to insure all elements are from a lower epoch - SetGlobalEpoch(GetGlobalEpoch() + 1); + SetActiveEpoch(isBarrierReached, GetThreadId()); m_managerLock.lock(); - HardQuiesce(m_totalLimboInuseElements); + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + m_GcQueues[queue].SetPerformGcEpoch(g_gcActiveEpoch); + inuseElements += m_GcQueues[queue].m_stats.m_totalLimboInuseElements; + m_GcQueues[queue].HardQuiesce(m_GcQueues[queue].m_stats.m_totalLimboInuseElements); + m_GcQueues[queue].RegisterDeletedSentinels(); + } + GcMaintenance(); m_managerLock.unlock(); #ifdef MOT_DEBUG - uint32_t inuseElements = m_totalLimboInuseElements; - uint32_t cleandObjects = inuseElements - m_totalLimboInuseElements; + uint64_t cleandObjects = inuseElements - GetTotalLimboInuseElements(); if (cleandObjects) { - MOT_LOG_INFO( - "Entity:%s THD_ID:%d cleaned %d elements from limbo!\n", enGcTypes[m_purpose], m_tid, (cleandObjects)); + MOT_LOG_DEBUG("Entity:%s THD_ID:%u cleaned %lu elements from limbo!", + enGcTypes[static_cast(m_purpose)], + m_tid, + cleandObjects); } #endif } @@ -380,208 +331,223 @@ public: * @param cb Callback function * @param objSize Size of the object */ - void GcRecordObject(uint32_t indexId, void* objectPtr, void* objectPool, DestroyValueCbFunc cb, uint32_t objSize) + void GcRecordObject(GC_QUEUE_TYPE m_type, uint32_t indexId, void* objectPtr, void* objectPool, + DestroyValueCbFunc cb, uint32_t objSize, GcEpochType csn = 0) { - if (m_isGcEnabled == false) { - return; + uint64_t epoch = csn; + if (!csn) { + epoch = GetGlobalEpoch(); } - if (m_limboTail->m_tail + 2 > LimboGroup::CAPACITY) { - bool res = RefillLimboGroup(); - if (res == false) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "GC Operation", "Failed to refill limbo group"); - return; - } - } - uint64_t epoch = GetGlobalEpoch(); - m_limboTail->PushBack(indexId, objectPtr, objectPool, cb, epoch); - ++m_totalLimboInuseElements; - m_totalLimboSizeInBytes += objSize; - m_totalLimboRetiredSizeInBytes += objSize; // stats - MemoryStatisticsProvider::m_provider->AddGCRetiredBytes(objSize); + + m_GcQueues[static_cast(m_type)].PushBack(indexId, objectPtr, objectPool, cb, objSize, epoch); + MemoryStatisticsProvider::GetInstance().AddGCRetiredBytes(objSize); } - /** @brief Try to upgrade the global epoch or let other thread do it */ - void SetGlobalEpoch(GcEpochType e) + /** @brief Try to upgrade the global minimum or let other thread do it */ + void SetActiveEpoch(bool& isBarrierReached, const uint16_t tid) { + isBarrierReached = false; + uint32_t activeConnections = 0; + uint16_t latestActiveTid = static_cast(-1); bool rc = g_gcGlobalEpochLock.try_lock(); - if (rc == true) { - if (GcSignedEpochType(e - g_gcGlobalEpoch) > 0) { - g_gcGlobalEpoch = e; - g_gcActiveEpoch = GcManager::MinActiveEpoch(); + if (rc) { + g_gcActiveEpoch = GcManager::MinActiveEpoch(activeConnections, latestActiveTid); + if (activeConnections <= 1) { + // Either no active connections or i am seeing myself + if (activeConnections == 0 or latestActiveTid == tid) { + // Self view + isBarrierReached = true; + } } g_gcGlobalEpochLock.unlock(); } } - /** @brief realloc limbo group */ - bool RefillLimboGroup(); - - /** @brief allocate limbo group */ - LimboGroup* CreateNewLimboGroup() - { - LimboGroup* group = m_limboGroupPool->Alloc(); - if (group == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Create LimboGroup", "Failed to create new LimboGroup in Thread %d", GetThreadId()); - } - return group; - } - - /** @brief release limbo group */ - void DestroyLimboGroup(LimboGroup* obj) - { - m_limboGroupPool->Release(obj); - } - - /** @brief shrink limbogroups memory */ - inline void ShrinkMem() - { - if (m_limboGroupAllocations > 1) { - - LimboGroup* temp = m_limboTail->m_next; - LimboGroup* next = nullptr; - while (temp) { - next = temp->m_next; - DestroyLimboGroup(temp); - temp = next; - m_limboGroupAllocations--; - } - m_limboTail->m_next = nullptr; - } - } - - /** @brief null destructor callback function - * @param buf Ignored - * @param buf2 Ignored - * @param dropIndex Ignored - * @return 0 (for success) - */ - static uint32_t NullDtor(void* buf, void* buf2, bool dropIndex) - { - return 0; - } - /** @brief API for global index cleanup - used by vacuum/drop table * @param indexId Index identifier * @return True for success */ - static inline bool ClearIndexElements(uint32_t indexId, bool dropIndex = true); + static inline void ClearIndexElements( + uint32_t indexId, GC_OPERATION_TYPE oper = GC_OPERATION_TYPE::GC_OPER_DROP_INDEX); - int GetFreeCount() const + uint64_t inline GetTotalLimboInuseElements() const { - return m_rcuFreeCount; + uint64_t totalLimboInuseElements = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + totalLimboInuseElements += m_GcQueues[queue].m_stats.m_totalLimboInuseElements; + } + return totalLimboInuseElements; } + uint64_t inline GetLimboInuseElementsByQueue(GC_QUEUE_TYPE queue) const + { + return m_GcQueues[static_cast(queue)].m_stats.m_totalLimboInuseElements; + } + + uint32_t inline GetLimboGroupAllocations() const + { + uint32_t limboGroupAllocations = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + limboGroupAllocations += m_GcQueues[queue].m_stats.m_limboGroupAllocations; + } + return limboGroupAllocations; + } + + void inline ValidateAllocations() const + { + MOT_ASSERT(m_limboGroupAllocations - m_reservedManager.GetLimboAlloctions() == GetLimboGroupAllocations()); + } + + uint32_t GetFreeAllocations() const + { + uint32_t freeAllocations = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + freeAllocations += m_GcQueues[queue].GetFreeAllocations(); + } + return freeAllocations; + } + + uint32_t GetFreeAllocationsPerQueue(GC_QUEUE_TYPE queueId) const + { + return m_GcQueues[static_cast(queueId)].GetFreeAllocations(); + } + + uint64_t inline GetTotalLimboReclaimedSizeInBytes() + { + uint64_t totalLimboReclaimedSizeInBytes = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + totalLimboReclaimedSizeInBytes += m_GcQueues[queue].m_stats.m_totalLimboReclaimedSizeInBytes; + } + return totalLimboReclaimedSizeInBytes; + } + + uint64_t inline GetTotalLimboRetiredSizeInBytes() + { + uint64_t totalLimboRetiredSizeInBytes = 0; + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + totalLimboRetiredSizeInBytes += m_GcQueues[queue].m_stats.m_totalLimboRetiredSizeInBytes; + } + return totalLimboRetiredSizeInBytes; + } + + inline void GcMaintenance() + { + for (uint32_t queue = 0; queue < m_GcQueues.size(); ++queue) { + m_GcQueues[queue].Maintenance(); + } + } + + void GcCleanRollbackMemory() + { + uint32_t allocations = m_reservedManager.GetFreeAllocations(); + if (allocations) { + LimboGroup* head = m_reservedManager.AllocLimboGroup(); + while (head) { + DestroyLimboGroup(head); + head = m_reservedManager.AllocLimboGroup(); + } + m_reservedManager.Clear(); + if (allocations > GcQueue::LIMBO_GROUP_SHRINK_THRESHOLD) { + ClearLimboGroupCache(); + } + } + } + + static constexpr int LIMBO_GROUP_INIT_SIZE = 4; + static constexpr int64_t INVALID_EPOCH = -1; + static constexpr uint16_t GC_CLEAN_SLEEP = 20 * 1000; + static constexpr uint16_t GC_CLEAN_SESSION_SLEEP = 50; + private: + /** @var Vector of priority queues */ + std::vector m_GcQueues; + /** @var Current snapshot of the global epoch */ GcEpochType m_gcEpoch; - /** @var Calculated perform epoch */ - GcEpochType m_performGcEpoch; - - /** @var Limbo group HEAD */ - LimboGroup* m_limboHead = nullptr; - - /** @var Limbo group TAIL */ - LimboGroup* m_limboTail = nullptr; - - /** @var Limbo group memory pool */ - ObjAllocInterface* m_limboGroupPool = nullptr; - - /** @var Next manager in the global list */ - GcManager* m_next = nullptr; - /** @var Manager local row */ GcLock m_managerLock; - /** @var Flag for feature availability */ - bool m_isGcEnabled = false; + /** @var Limbo group memory pool */ + ObjAllocInterface* m_limboGroupPool = nullptr; - /** @var Flag to signal if we started a transaction */ - bool m_isTxnStarted = false; + GcReservedMemoryPool m_reservedManager; - /** @var RCU free count */ - uint16_t m_rcuFreeCount; - - /** @var Total limbo elements in use */ - uint32_t m_totalLimboInuseElements; - - /**@var Total size of limbo in bytes */ - uint32_t m_totalLimboSizeInBytes; - - /** @var Total clean object by clean index in bytes */ - uint32_t m_totalLimboSizeInBytesByCleanIndex; - - /** @var Total reclaimed objects in bytes */ - uint32_t m_totalLimboReclaimedSizeInBytes; - - /** @var Total retired object in bytes */ - uint32_t m_totalLimboRetiredSizeInBytes; - - /** @var Limbo size limit */ - uint32_t m_limboSizeLimit; - - /** @var Limbo size hard limit */ - uint32_t m_limboSizeLimitHigh; + /** @var Next manager in the global list */ + GcManager* m_next = nullptr; /** @var Number of allocations */ - uint16_t m_limboGroupAllocations; + uint32_t m_limboGroupAllocations; /** @var Thread identification */ uint16_t m_tid; + /** @var Flag to signal if we took a snapshot */ + bool m_isTxnSnapshotTaken = false; + + /** @var Flag to signal if we started a transaction */ + bool m_isGcSessionStarted = false; + + /** @var Flag to signal if we reached a threshold */ + bool m_isThresholdReached = false; + /** @var GC manager type */ GC_TYPE m_purpose; + bool m_global; + /** @brief Calculate the minimum epoch among all active GC Managers. * @return The minimum epoch among all active GC Managers. */ - static inline GcEpochType MinActiveEpoch(); - - inline uint32_t GetOccupiedElements() const - { - return m_totalLimboInuseElements; - } + static inline GcEpochType MinActiveEpoch(uint32_t& activeConnections, uint16_t& tid); /** @brief Constructor */ - inline GcManager(GC_TYPE purpose, int index, int rcuMaxFreeCount); + inline GcManager(GC_TYPE purpose, int threadId, bool global = false); /** @brief Initialize GC Manager's structures. * @return True for success */ bool Initialize(); - /** @brief Clean\reclaim elements from Limbo groups */ - void HardQuiesce(uint32_t numOfElementsToClean); - /** @brief Remove all elements of elements of a specific index from all Limbo groups and reclaim them */ - void CleanIndexItems(uint32_t indexId, bool dropIndex); - friend struct LimboGroup; + void CleanIndexItems(uint32_t indexId, GC_OPERATION_TYPE oper); DECLARE_CLASS_LOGGER() }; -inline GcEpochType GcManager::MinActiveEpoch() +inline GcEpochType GcManager::MinActiveEpoch(uint32_t& activeConnections, uint16_t& tid) { - GcEpochType ae = g_gcGlobalEpoch; + GcEpochType minimalEpoch = static_cast(INVALID_EPOCH); + activeConnections = 0; for (GcManager* ti = allGcManagers; ti; ti = ti->Next()) { Prefetch((const void*)ti->Next()); - GcEpochType te = ti->m_gcEpoch; - if (te && (GcSignedEpochType(te - ae) < 0)) - ae = te; + GcEpochType currentEpoch = ti->m_gcEpoch; + if (currentEpoch > 0) { + activeConnections++; + tid = ti->GetThreadId(); + if (currentEpoch < minimalEpoch) { + minimalEpoch = currentEpoch; + } + } } - return ae; + + // If all sessions are closed the min_epoch is the current clock + // We can declare that we passed a barrier + if (GcSignedEpochType(minimalEpoch) == INVALID_EPOCH) { + return GetGlobalEpoch(); + } + + return minimalEpoch; } -inline bool GcManager::ClearIndexElements(uint32_t indexId, bool dropIndex) +inline void GcManager::ClearIndexElements(uint32_t indexId, GC_OPERATION_TYPE oper) { g_gcGlobalEpochLock.lock(); for (GcManager* gcManager = allGcManagers; gcManager; gcManager = gcManager->Next()) { Prefetch((const void*)gcManager->Next()); - gcManager->CleanIndexItems(indexId, dropIndex); + gcManager->CleanIndexItems(indexId, oper); } g_gcGlobalEpochLock.unlock(); - return true; } } // namespace MOT #endif /* MM_GC_MANAGER */ diff --git a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.cpp b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.cpp new file mode 100644 index 000000000..9f01b4e1a --- /dev/null +++ b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mm_gc_manager.h + * Garbage-collector queues per-session. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mm_gc_queue.h" +#include "sentinel.h" +#include "index.h" +#include + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(GcQueue, GC); + +class Sentinel; +class Index; + +const char* const GcQueue::enGcQueue[] = { + stringify(DELETE_QUEUE), stringify(VERSION_QUEUE), stringify(UPDATE_COLUMN_QUEUE), stringify(GENERIC_QUEUE)}; + +/** @var Current lowest epoch among all active GC Managers */ +volatile GcEpochType g_gcActiveEpoch = 0; + +inline uint64_t LimboGroup::CleanUntil(GcQueue& ti, GcEpochType epochBound, uint64_t count) +{ + EpochType csn = 0; + uint64_t size = 0; + GC_OPERATION_TYPE gcOper = GC_OPERATION_TYPE::GC_OPER_LOCAL; + while (m_head != m_tail) { + LimboElement* elem = &m_elements[m_head]; + if (elem->m_objectPtr) { + if (elem->m_cb != GcQueue::NullDtor) { + size = elem->m_cb(elem, &gcOper, ti.GetDeleteVector()); + MemoryStatisticsProvider::GetInstance().AddGCReclaimedBytes(size); + ti.m_stats.m_totalLimboSizeInBytes -= size; + ti.m_stats.m_totalLimboReclaimedSizeInBytes += size; // stats + elem->m_cb = GcQueue::NullDtor; + --count; + if (!count) { + m_elements[m_head].m_objectPtr = nullptr; + m_elements[m_head].m_csn = csn; + break; + } + } + } else { + csn = m_elements[m_head].m_csn; + if (SignedEpochType(epochBound - csn) < 0) { + break; + } + } + ++m_head; + } + if (m_head == m_tail) { + m_head = m_tail = 0; + } + return count; +} + +inline unsigned LimboGroup::CleanIndexItemPerGroup(GcQueue& ti, uint32_t indexId, GC_OPERATION_TYPE oper) +{ + unsigned gHead = m_head; + uint32_t size = 0; + uint32_t itemCleaned = 0; + + while (gHead != m_tail) { + if (m_elements[gHead].m_objectPtr != nullptr && m_elements[gHead].m_indexId == indexId && + m_elements[gHead].m_cb != GcQueue::NullDtor) { + size = m_elements[gHead].m_cb(&m_elements[gHead], &oper, ti.GetDeleteVector()); + MemoryStatisticsProvider::GetInstance().AddGCReclaimedBytes(size); + ti.m_stats.m_totalLimboSizeInBytesByCleanIndex += size; + m_elements[gHead].m_cb = GcQueue::NullDtor; + itemCleaned++; + } + ++gHead; + } + return itemCleaned; +} + +uint32_t LimboGroup::GetListLength() +{ + uint32_t count = 0; + LimboGroup* head = this; + while (head) { + count++; + head = head->m_next; + } + return count; +} + +GcQueue::GcQueue(const GcQueueEntry& entry) : m_queueType(entry.m_type) +{ + m_stats.m_limboSizeLimit = entry.m_limboSizeLimit; + m_stats.m_limboSizeLimitHigh = entry.m_limboSizeLimitHigh; + m_stats.m_rcuFreeCount = entry.m_rcuFreeCount; +} + +GcQueue::~GcQueue() +{ + if (m_deleteVector != nullptr) { + delete m_deleteVector; + m_deleteVector = nullptr; + } + + m_manager = nullptr; + m_limboHead = nullptr; + m_limboTail = nullptr; +} + +bool GcQueue::Initialize(GcManager* manager) +{ + m_manager = manager; + + LimboGroup* limboGroup = manager->CreateNewLimboGroup(); + if (limboGroup == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Create GC Context", + "Failed to allocate %u bytes for limbo group", + (unsigned)sizeof(LimboGroup)); + return false; + } + + m_limboHead = m_limboTail = limboGroup; + m_stats.m_limboGroupAllocations = 1; + + m_deleteVector = new (std::nothrow) DeleteVector(); + if (m_deleteVector == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create GC Context", "Failed to allocate deleteList for limbo group"); + return false; + } + + if (!m_reservedManager.initialize()) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create GC Context", "Failed to create GcQueueReservedMemory"); + return false; // safe cleanup during destroy + } + + m_deleteVector->clear(); + if (manager->GetGcType() == GcManager::GC_TYPE::GC_RECOVERY) { + m_stats.m_limboSizeLimit = RECOVERY_LIMBO_SIZE_LIMIT; + m_stats.m_limboSizeLimitHigh = 2 * RECOVERY_LIMBO_SIZE_LIMIT; + } + return true; +} + +bool GcQueue::RefillLimboGroup() +{ + LimboGroup* limboGroup = nullptr; + if (!m_limboTail->m_next) { + // First Tier - rollback pool + limboGroup = m_manager->AllocLimboGroupFromReserved(); + if (limboGroup == nullptr) { + // Second Tier - queue pool + limboGroup = m_reservedManager.AllocLimboGroup(); + if (limboGroup == nullptr) { + // Third Tier - global pool + limboGroup = m_manager->CreateNewLimboGroup(); + if (limboGroup == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "GC Refill Limbo Group", + "Failed to allocate %u bytes for limbo group", + (unsigned)sizeof(LimboGroup)); + return false; + } else { + m_stats.m_limboGroupAllocations++; + } + } + } else { + // Update the stats - current queue using manager reserve + m_stats.m_limboGroupAllocations++; + } + m_limboTail->m_next = limboGroup; + } + m_limboTail = m_limboTail->m_next; + MOT_ASSERT(m_limboTail->m_head == 0 && m_limboTail->m_tail == 0); + return true; +} + +bool GcQueue::AllocLimboGroups(uint32_t limboGroups, uint32_t totalElements, bool reserveGroups) +{ + LimboGroup* currentTail = nullptr; + LimboGroup* currentHead = nullptr; + LimboGroup* limboGroup = nullptr; + bool isAllocatedfromReserved = false; + for (uint32_t group = 0; group < limboGroups; group++) { + isAllocatedfromReserved = false; + if (m_queueType == GC_QUEUE_TYPE::GENERIC_QUEUE and !reserveGroups) { + // If we are using the generic queue - try first to allocate from reserved pool + limboGroup = m_reservedManager.AllocLimboGroup(); + if (limboGroup) { + isAllocatedfromReserved = true; + } + } + if (!isAllocatedfromReserved) { + limboGroup = m_manager->CreateNewLimboGroup(); + } + if (limboGroup != nullptr) { + if (!currentTail) { + currentTail = limboGroup; + currentHead = currentTail; + } else { + limboGroup->m_next = currentHead; + currentHead = limboGroup; + } + if (!isAllocatedfromReserved) { + m_stats.m_limboGroupAllocations++; + } + } else { + // Release memory + while (currentHead) { + LimboGroup* tmp = currentHead; + currentHead = currentHead->m_next; + m_manager->DestroyLimboGroup(tmp); + m_stats.m_limboGroupAllocations--; + } + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "GC Reserve Limbo Group", + "Failed to allocate %u bytes for limbo group", + (unsigned)sizeof(LimboGroup)); + return false; + } + } + + if (reserveGroups) { + m_reservedManager.ReserveGCMemory(currentHead, totalElements); + } else { + currentTail->m_next = m_limboTail->m_next; + m_limboTail->m_next = currentHead; + } + + return true; +} + +bool GcQueue::ReserveGCMemory(uint32_t elements, bool reserveGroups) +{ + uint32_t limboGroups = 0; + uint32_t freeElements = 0; + uint64_t totalElements = elements; + if (m_queueType == GC_QUEUE_TYPE::GENERIC_QUEUE) { + totalElements *= MASSTREE_RESERVE; + } + + if (reserveGroups) { + freeElements = m_reservedManager.GetFreeAllocations(); + if (freeElements > totalElements) { + m_reservedManager.ReserveGCMemory(nullptr, static_cast(totalElements)); + return true; + } else { + totalElements -= freeElements; + limboGroups = static_cast(std::ceil(double(totalElements) / LimboGroup::CAPACITY)); + } + } else { + freeElements = GetFreeAllocations(); + if (freeElements < (totalElements + 2)) { + limboGroups = + static_cast(std::ceil(double(totalElements + 2 - freeElements) / LimboGroup::CAPACITY)); + } + } + + if (limboGroups == 0) { + return true; + } + + return AllocLimboGroups(limboGroups, static_cast(totalElements), reserveGroups); +} + +void GcQueue::HardQuiesce(uint64_t numOfElementsToClean) +{ + LimboGroup* emptyHead = nullptr; + LimboGroup* emptyTail = nullptr; + uint64_t count = numOfElementsToClean; + uint64_t locCount = 0; + + MOT_ASSERT(GetPerformGcEpoch() > 0); + GcEpochType epochBound = GetPerformGcEpoch() - 1; + if (m_limboHead->m_head == m_limboHead->m_tail || GcSignedEpochType(epochBound - m_limboHead->FirstEpoch()) < 0) { + goto done; + } + + // clean [limbo_head_, limbo_tail_] + while (count) { + locCount = m_limboHead->CleanUntil(*this, epochBound, count); + m_stats.m_totalLimboInuseElements -= (count - locCount); + count = locCount; + if (m_limboHead->m_head != m_limboHead->m_tail) { + break; + } + if (emptyHead == nullptr) { + emptyHead = m_limboHead; + } + emptyTail = m_limboHead; + if (m_limboHead == m_limboTail) { + m_limboHead = m_limboTail = emptyHead; + goto done; + } + m_limboHead = m_limboHead->m_next; + } + // hook empties after limbo_tail_ + if (emptyHead != nullptr) { + emptyTail->m_next = m_limboTail->m_next; + m_limboTail->m_next = emptyHead; + } + +done: + MOT_LOG_DEBUG( + "Entity:%s cleaned items = %lu", enGcQueue[static_cast(m_queueType)], numOfElementsToClean - count); +} + +void GcQueue::RunQuiesce() +{ + MOT_LOG_DEBUG("Entity:%s min_csn = %lu performCSN = %lu, firstEpoch = %lu", + enGcQueue[static_cast(m_queueType)], + g_gcActiveEpoch, + m_performGcEpoch, + m_limboHead->FirstEpoch()); + + // Perform reclamation if possible + HardQuiesce(m_stats.m_rcuFreeCount); + + // If we still exceed the high size limit, (e.g. we had a major gc addition in this txn run), clean all elements + // (up to epoch limitation) + if (m_stats.m_totalLimboSizeInBytes > m_stats.m_limboSizeLimitHigh) { + HardQuiesce(m_stats.m_totalLimboInuseElements); + MOT_LOG_DEBUG("Thread %d Entity:%s Reached high-threshold m_totalLimboSizeInBytes = %lu ", + m_manager->GetThreadId(), + enGcQueue[static_cast(m_queueType)], + m_stats.m_totalLimboSizeInBytes); + } + + RegisterDeletedSentinels(); + ShrinkMem(); +} + +void GcQueue::RegisterDeletedSentinels() +{ + if (m_deleteVector->size() == 0) { + return; + } + + Sentinel* s = nullptr; + uint64_t csn = GetGlobalEpoch(); + for (void* sentinel : *m_deleteVector) { + s = static_cast(sentinel); + Index* index = s->GetIndex(); + m_manager->GcRecordObject(GC_QUEUE_TYPE::GENERIC_QUEUE, + index->GetIndexId(), + s, + nullptr, + Index::SentinelDtor, + SENTINEL_SIZE(index), + csn); + } + + m_deleteVector->clear(); +} +uint32_t GcQueue::CleanIndexItems(uint32_t indexId, GC_OPERATION_TYPE oper) +{ + uint32_t counter = 0; + LimboGroup* head = m_limboHead; + LimboGroup* tail = m_limboTail; + while (head != nullptr) { + counter += head->CleanIndexItemPerGroup(*this, indexId, oper); + head = head->m_next; + } + + if (oper != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + RegisterDeletedSentinels(); + } + + if (oper == GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + MOT_ASSERT(m_deleteVector->size() == 0); + } + + // Update values from cleanIndexItemPerGroup flow + if (m_stats.m_totalLimboSizeInBytesByCleanIndex) { + m_stats.m_totalLimboInuseElements -= counter; + m_stats.m_totalLimboSizeInBytes -= m_stats.m_totalLimboSizeInBytesByCleanIndex; + m_stats.m_totalLimboReclaimedSizeInBytes += m_stats.m_totalLimboSizeInBytesByCleanIndex; + m_stats.m_totalLimboSizeInBytesByCleanIndex = 0; + } + + return counter; +} + +void GcQueue::ShrinkMem() +{ + if (m_stats.m_limboGroupAllocations > LIMBO_GROUP_SHRINK_THRESHOLD) { + LimboGroup* temp = m_limboTail->m_next; + LimboGroup* next = nullptr; + bool isCleaned = false; + m_manager->ValidateAllocations(); + while (temp) { + next = temp->m_next; + m_manager->DestroyLimboGroup(temp); + temp = next; + MOT_ASSERT(m_stats.m_limboGroupAllocations > 0); + m_stats.m_limboGroupAllocations--; + isCleaned = true; + } + m_manager->ValidateAllocations(); + if (m_queueType == GC_QUEUE_TYPE::GENERIC_QUEUE and m_reservedManager.GetFreeAllocations()) { + uint64_t deletedElements = m_manager->GetLimboInuseElementsByQueue(GC_QUEUE_TYPE::DELETE_QUEUE); + if (!m_stats.m_totalLimboInuseElements and !deletedElements) { + LimboGroup* head = m_reservedManager.AllocLimboGroup(); + while (head) { + m_manager->DestroyLimboGroup(head); + m_stats.m_limboGroupAllocations--; + head = m_reservedManager.AllocLimboGroup(); + } + m_reservedManager.Clear(); + } + } + m_manager->ValidateAllocations(); + if (isCleaned) { + m_limboTail->m_next = nullptr; + m_manager->ClearLimboGroupCache(); + } + m_manager->ValidateAllocations(); + } +} + +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.h b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.h new file mode 100644 index 000000000..0f5fd060c --- /dev/null +++ b/src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.h @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mm_gc_manager.h + * Garbage-collector queues per-session. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/memory/garbage_collector/mm_gc_queue.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef MM_GC_QUEUE_H +#define MM_GC_QUEUE_H + +#include "global.h" +#include "spin_lock.h" +#include "utilities.h" +#include "memory_statistics.h" +#include "mm_session_api.h" +#include +#include +#include + +namespace MOT { + +class GcManager; +class GcQueue; + +using GcEpochType = uint64_t; +using GcSignedEpochType = int64_t; +using GcLock = MOT::spin_lock; + +/** @def GC callback function signature */ +typedef uint32_t (*DestroyValueCbFunc)(void*, void*, void*); + +/** @def GC epoch type */ +using EpochType = GcEpochType; + +/** @def GC signed epoch type */ +using SignedEpochType = GcSignedEpochType; + +/** @def GC callback function signature */ +enum class GC_QUEUE_TYPE : uint8_t { DELETE_QUEUE, VERSION_QUEUE, UPDATE_COLUMN_QUEUE, GENERIC_QUEUE, GC_QUEUES }; + +/** @def GC operation type */ +enum class GC_OPERATION_TYPE : uint8_t { GC_OPER_LOCAL, GC_OPER_DROP_INDEX, GC_OPER_TRUNCATE, GC_ALTER_TABLE }; + +/** @def GC RC from RunQuicese operation */ +enum class GC_QUEUE_RC : uint8_t { RC_QUEUE_NONE, RC_QUEUE_PARTIAL, RC_QUEUE_FULL, RC_QUEUE_HIGH_LIMIT }; + +/** + * @struct GcQueueEntry + * @brief Contains all the necessary members to describe the queue + */ +struct GcQueueEntry { + GC_QUEUE_TYPE m_type; + uint32_t m_limboSizeLimit; + uint32_t m_limboSizeLimitHigh; + uint32_t m_rcuFreeCount; +}; + +/** + * @struct LimboElement + * @brief Contains all the necessary members to reclaim the object + */ +struct PACKED LimboElement { + void* m_objectPtr; + + void* m_objectPool; + + DestroyValueCbFunc m_cb; + + EpochType m_csn; + + uint32_t m_indexId; +}; + +/** + * @struct LimboGroup + * @brief Contains capacity elements and handle push/pop operations + */ +struct LimboGroup { + static constexpr uint16_t LIMBO_PAGE_SIZE = 4096 - (sizeof(EpochType) + sizeof(LimboGroup*) + 2 * sizeof(unsigned)); + + static constexpr uint32_t CAPACITY = (LIMBO_PAGE_SIZE) / sizeof(LimboElement); + + EpochType m_epoch; + + LimboGroup* m_next = nullptr; + + unsigned m_head = 0; + + unsigned m_tail = 0; + + LimboElement m_elements[CAPACITY]; + + LimboGroup() : m_epoch(0), m_next(nullptr), m_head(0), m_tail(0) + {} + + EpochType FirstEpoch() const + { + MOT_ASSERT(m_head != m_tail); + return m_elements[m_head].m_csn; + } + + void Reset() + { + m_head = 0; + m_tail = 0; + m_next = nullptr; + } + + bool isGroupFull() const + { + return (m_tail + 1 == CAPACITY); + } + + uint32_t GetListLength(); + + /** + * @brief Push an element to the list, if the epoch is new create a new dummy element + * @param indexId Element index-id + * @param objectPtr Pointer to reclaim + * @param objectPool Memory pool (optional) + * @param cb Callback function + * @param epoch Recorded epoch + */ + void PushBack(uint32_t indexId, void* objectPtr, void* objectPool, DestroyValueCbFunc cb, GcEpochType epoch) + { + MOT_ASSERT(m_tail + 2 <= CAPACITY); + if (m_head == m_tail || m_epoch != epoch) { + m_elements[m_tail].m_objectPtr = nullptr; + m_elements[m_tail].m_csn = epoch; + m_epoch = epoch; + ++m_tail; + } + m_elements[m_tail].m_indexId = indexId; + m_elements[m_tail].m_objectPtr = objectPtr; + m_elements[m_tail].m_csn = epoch; + m_elements[m_tail].m_objectPool = objectPool; + m_elements[m_tail].m_cb = cb; + ++m_tail; + } + + /** @brief Clean all element until epochBound + * @param ti GcManager object to clean + * @param epochBound Epoch boundary to limit cleanup + * @param count Max items to clean + */ + inline uint64_t CleanUntil(GcQueue& ti, GcEpochType epochBound, uint64_t count); + + /** + * @brief Clean All elements from the current GC manager tagged with index_id + * @param ti GcManager object to clean + * @param indexId Index Identifier to clean + * @return Number of elements cleaned + */ + inline unsigned CleanIndexItemPerGroup(GcQueue& ti, uint32_t indexId, GC_OPERATION_TYPE oper); +}; + +class GcReservedMemoryPool { +public: + using ReservedList = std::list; + GcReservedMemoryPool() : isInitialized(false), m_limboReservedMemory(nullptr) + {} + + ~GcReservedMemoryPool() + { + if (isInitialized) { + delete m_limboReservedMemory; + isInitialized = false; + } + + m_limboReservedMemory = nullptr; + } + + bool initialize() + { + if (!isInitialized) { + m_limboReservedMemory = new (std::nothrow) ReservedList(); + if (m_limboReservedMemory == nullptr) { + return false; + } + isInitialized = true; + } + return true; + } + + void ReserveGCMemory(LimboGroup* list, uint32_t count = 0) + { + LimboGroup* tmp = nullptr; + MOT_ASSERT(m_limboReservedMemory != nullptr); + if (list) { + while (list) { + tmp = list; + list = list->m_next; + tmp->Reset(); + MOT_ASSERT(tmp->m_head == 0 && tmp->m_tail == 0); + m_limboReservedMemory->push_back(tmp); + } + } + registeredElements += count; + groupAllocations += static_cast(std::ceil(double(count) / LimboGroup::CAPACITY)); + } + + uint32_t GetFreeAllocations() const + { + return groupAllocations * LimboGroup::CAPACITY - registeredElements; + } + + uint32_t GetLimboAlloctions() const + { + return groupAllocations; + } + + LimboGroup* AllocLimboGroup() + { + LimboGroup* e = nullptr; + if (!m_limboReservedMemory->empty()) { + e = m_limboReservedMemory->front(); + m_limboReservedMemory->pop_front(); + groupAllocations--; + if (registeredElements > LimboGroup::CAPACITY) { + registeredElements -= LimboGroup::CAPACITY; + } else { + registeredElements = 0; + } + } + return e; + } + + void Clear() + { + MOT_ASSERT(m_limboReservedMemory->size() == 0); + groupAllocations = 0; + registeredElements = 0; + } + +private: + bool isInitialized = false; + uint32_t groupAllocations = 0; + uint32_t registeredElements = 0; + ReservedList* m_limboReservedMemory = nullptr; +}; + +class GcQueue { +public: + using DeleteVector = std::vector; + + struct GcStats { + /** @var Total limbo elements in use */ + uint64_t m_totalLimboInuseElements; + + /**@var Total size of limbo in bytes */ + uint64_t m_totalLimboSizeInBytes; + + /** @var Total clean object by clean index in bytes */ + uint64_t m_totalLimboSizeInBytesByCleanIndex; + + /** @var Total reclaimed objects in bytes */ + uint64_t m_totalLimboReclaimedSizeInBytes; + + /** @var Total retired object in bytes */ + uint64_t m_totalLimboRetiredSizeInBytes; + + /** @var Limbo size limit */ + uint32_t m_limboSizeLimit; + + /** @var Limbo size hard limit */ + uint32_t m_limboSizeLimitHigh; + + /** @var Number of allocations */ + uint32_t m_limboGroupAllocations; + + /** @var RCU free count */ + uint32_t m_rcuFreeCount; + }; + + explicit GcQueue(const GcQueueEntry& entry); + ~GcQueue(); + + /** @brief Initialize the GC Queue */ + bool Initialize(GcManager* manager); + + /** @brief Refill GC Queue */ + bool RefillLimboGroup(); + + /** @brief Reserve GC Memory */ + bool ReserveGCMemory(uint32_t elements, bool reserveGroups = false); + + bool AllocLimboGroups(uint32_t limboGroups, uint32_t totalElements, bool reserveGroups); + + /** @brief Clean\reclaim elements from Limbo groups if possible + * @return Queue size compared to threshold + */ + void RunQuiesce(); + + /** @brief Clean\reclaim elements from Limbo groups + * @return Queue size compared to threshold + */ + void HardQuiesce(uint64_t numOfElementsToClean); + + bool IsThresholdReached() const + { + if (m_stats.m_totalLimboSizeInBytes > m_stats.m_limboSizeLimit) { + return true; + } + return false; + } + + inline EpochType FirstEpoch() const + { + return m_limboHead->FirstEpoch(); + } + + /** @brief Push new element to the GC Queue */ + inline void PushBack( + uint32_t indexId, void* objectPtr, void* objectPool, DestroyValueCbFunc cb, uint32_t objSize, GcEpochType csn) + { + if (m_limboTail->m_tail + 2 > LimboGroup::CAPACITY) { + bool res = RefillLimboGroup(); + if (!res) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "GC Operation", "Failed to refill limbo group"); + return; + } + } + m_limboTail->PushBack(indexId, objectPtr, objectPool, cb, csn); + ++m_stats.m_totalLimboInuseElements; + m_stats.m_totalLimboSizeInBytes += objSize; + m_stats.m_totalLimboRetiredSizeInBytes += objSize; // stats + } + + /** @brief null destructor callback function + * @param buf Ignored + * @param buf2 Ignored + * @param dropIndex Ignored + * @return 0 (for success) + */ + static uint32_t NullDtor(void* buf, void* dropIndex, void* aux) + { + return 0; + } + + /** @brief Perform memory maintenance on Queue */ + void Maintenance() + { + ShrinkMem(); + } + + /** @brief Register all the deleted elements to the generic queue priority 3 */ + void RegisterDeletedSentinels(); + + /** @brief Clean all items for current index id + * @param indexId + * @param oper type of GC operation to perform + * @return # of cleaned elements + */ + uint32_t CleanIndexItems(uint32_t indexId, GC_OPERATION_TYPE oper); + + DeleteVector* GetDeleteVector() + { + return m_deleteVector; + } + + GcEpochType GetPerformGcEpoch() const + { + return m_performGcEpoch; + } + + void SetPerformGcEpoch(GcEpochType e) + { + m_performGcEpoch = e; + } + + uint32_t GetFreeAllocations() const + { + uint32_t freeAllocations = (LimboGroup::CAPACITY - m_limboTail->m_tail); + LimboGroup* next = m_limboTail->m_next; + while (next) { + freeAllocations += LimboGroup::CAPACITY; + next = next->m_next; + } + return freeAllocations; + } + + /** @var Queue statistics */ + GcStats m_stats = {0}; + + static constexpr uint32_t LIMBO_GROUP_SHRINK_THRESHOLD = 10; + + static constexpr uint32_t MASSTREE_RESERVE = 2; + + static constexpr uint32_t RECOVERY_LIMBO_SIZE_LIMIT = 4096; + + static const char* const enGcQueue[]; + +private: + /** @var Calculated perform epoch */ + GcEpochType m_performGcEpoch = 0; + + /** @var Limbo group HEAD */ + LimboGroup* m_limboHead = nullptr; + + /** @var Limbo group TAIL */ + LimboGroup* m_limboTail = nullptr; + + /** @var Limbo group TAIL */ + DeleteVector* m_deleteVector = nullptr; + + /** @var pointer to manager */ + GcManager* m_manager = nullptr; + + GcReservedMemoryPool m_reservedManager; + + /** @var Queue type */ + GC_QUEUE_TYPE m_queueType; + + /** @brief shrink limbo groups memory */ + void ShrinkMem(); + + DECLARE_CLASS_LOGGER() +}; + +} // namespace MOT +#endif /* MM_GC_QUEUE */ diff --git a/src/gausskernel/storage/mot/core/memory/memory_statistics.cpp b/src/gausskernel/storage/mot/core/memory/memory_statistics.cpp index bd7ee3b60..73da508bc 100644 --- a/src/gausskernel/storage/mot/core/memory/memory_statistics.cpp +++ b/src/gausskernel/storage/mot/core/memory/memory_statistics.cpp @@ -180,18 +180,18 @@ DetailedMemoryStatisticsProvider::DetailedMemoryStatisticsProvider() DetailedMemoryStatisticsProvider::~DetailedMemoryStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void DetailedMemoryStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool DetailedMemoryStatisticsProvider::CreateInstance() @@ -240,9 +240,9 @@ void DetailedMemoryStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableDetailedMemoryStatistics) { m_enable = GetGlobalConfiguration().m_enableDetailedMemoryStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } @@ -256,18 +256,18 @@ MemoryStatisticsProvider::MemoryStatisticsProvider() MemoryStatisticsProvider::~MemoryStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void MemoryStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool MemoryStatisticsProvider::CreateInstance() @@ -314,9 +314,9 @@ void MemoryStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableMemoryStatistics) { m_enable = GetGlobalConfiguration().m_enableMemoryStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } diff --git a/src/gausskernel/storage/mot/core/memory/memory_statistics.h b/src/gausskernel/storage/mot/core/memory/memory_statistics.h index eb0fda6f8..2e483d110 100644 --- a/src/gausskernel/storage/mot/core/memory/memory_statistics.h +++ b/src/gausskernel/storage/mot/core/memory/memory_statistics.h @@ -30,7 +30,7 @@ #include "iconfig_change_listener.h" #include "numeric_statistic_variable.h" #include "statistics_provider.h" -#include "stats/frequency_statistic_variable.h" +#include "frequency_statistic_variable.h" #include "typed_statistics_generator.h" #include "mm_def.h" @@ -289,13 +289,6 @@ private: }; class DetailedMemoryStatisticsProvider : public StatisticsProvider, public IConfigChangeListener { -private: - DetailedMemoryStatisticsProvider(); - virtual ~DetailedMemoryStatisticsProvider(); - - /** @brief Registers the provider in the manager. */ - void RegisterProvider(); - public: /** * @brief Creates singleton instance. Must be called once during engine startup. @@ -427,22 +420,20 @@ public: */ void OnConfigChange() override; -public: // special case for performance reasons: allow direct access to singleton - /** @var The single instance. */ - static DetailedMemoryStatisticsProvider* m_provider; - private: - static TypedStatisticsGenerator m_generator; -}; - -class MemoryStatisticsProvider : public StatisticsProvider, public IConfigChangeListener { -private: - MemoryStatisticsProvider(); - virtual ~MemoryStatisticsProvider(); + DetailedMemoryStatisticsProvider(); + ~DetailedMemoryStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); + /** @var The single instance. */ + static DetailedMemoryStatisticsProvider* m_provider; + + static TypedStatisticsGenerator m_generator; +}; + +class MemoryStatisticsProvider : public StatisticsProvider, public IConfigChangeListener { public: /** * @brief Creates singleton instance. Must be called once during engine startup. @@ -624,18 +615,23 @@ public: */ void OnConfigChange() override; -public: // special case for performance reasons: allow direct access to singleton - /** @var The single instance. */ - static MemoryStatisticsProvider* m_provider; - -private: - static TypedStatisticsGenerator m_generator; - protected: /** * @brief Print current memory status summary. */ - virtual void PrintStatisticsEx(); + void PrintStatisticsEx() override; + +private: + MemoryStatisticsProvider(); + ~MemoryStatisticsProvider() override; + + /** @brief Registers the provider in the manager. */ + void RegisterProvider(); + + /** @var The single instance. */ + static MemoryStatisticsProvider* m_provider; + + static TypedStatisticsGenerator m_generator; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/mm_api.cpp b/src/gausskernel/storage/mot/core/memory/mm_api.cpp index 1c76e5495..02ee44feb 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_api.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_api.cpp @@ -204,7 +204,7 @@ extern uint64_t MemGetCurrentGlobalMemoryBytes() { // we need only the first statistics slot (accumulation of all global pools) MemRawChunkPoolStats chunkPoolStats = {0}; - MemRawChunkStoreGetGlobalStats(&chunkPoolStats, 1); + (void)MemRawChunkStoreGetGlobalStats(&chunkPoolStats, 1); return chunkPoolStats.m_usedBytes; } @@ -239,19 +239,19 @@ extern "C" void MemDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemToString("Debug Dump", stringBuffer, MOT::MEM_REPORT_SUMMARY); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } extern "C" void MemAnalyze(void* address) { // find source chunk - fprintf(stderr, "Analysis report for address %p\n", address); - fprintf(stderr, "==============================\n"); + (void)fprintf(stderr, "Analysis report for address %p\n", address); + (void)fprintf(stderr, "==============================\n"); MOT::MemRawChunkHeader* chunk = (MOT::MemRawChunkHeader*)MOT::MemRawChunkDirLookup(address); if (chunk) { - fprintf( + (void)fprintf( stderr, "Found source chunk %p (chunk type: %s)\n", chunk, MOT::MemChunkTypeToString(chunk->m_chunkType)); // continue according to chunk type int found = 0; @@ -290,7 +290,7 @@ extern "C" void MemAnalyze(void* address) break; } if (!found) { - fprintf(stderr, "Found not found yet, searching in chunk store...\n"); + (void)fprintf(stderr, "Address not found yet, searching in chunk store...\n"); MemRawChunkStoreAnalyze(address); } } diff --git a/src/gausskernel/storage/mot/core/memory/mm_api.h b/src/gausskernel/storage/mot/core/memory/mm_api.h index dd2205771..10f62aed9 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_api.h +++ b/src/gausskernel/storage/mot/core/memory/mm_api.h @@ -28,6 +28,8 @@ #include "utilities.h" #include "string_buffer.h" #include "mm_def.h" +#include "mm_global_api.h" +#include "mm_session_api.h" namespace MOT { /** @@ -82,6 +84,94 @@ extern void MemPrint(const char* name, LogLevel logLevel, MemReportMode reportMo * @param[opt] reportMode Specifies the report mode. */ extern void MemToString(const char* name, StringBuffer* stringBuffer, MemReportMode reportMode = MEM_REPORT_SUMMARY); + +inline void* MemAlloc(uint64_t sizeBytes, bool global) +{ + if (global) { + return MemGlobalAlloc(sizeBytes); + } else { +#ifdef MEM_SESSION_ACTIVE + return MemSessionAlloc(sizeBytes); +#else + return malloc(sizeBytes); +#endif + } +} + +inline void* MemAllocAligned(uint64_t sizeBytes, uint32_t alignment, bool global) +{ + if (global) { + return MemGlobalAllocAligned(sizeBytes, alignment); + } else { +#ifdef MEM_SESSION_ACTIVE + return MemSessionAllocAligned(sizeBytes, alignment); +#else + return memalign(alignment, sizeBytes)); +#endif + } +} + +template +inline T* MemAllocObject(bool global, Args&&... args) +{ + T* object = nullptr; + void* buffer; + if (global) { + buffer = MemGlobalAlloc(sizeof(T)); + } else { +#ifdef MEM_SESSION_ACTIVE + buffer = MemSessionAlloc(sizeof(T)); +#else + buffer = malloc(sizeof(T)); +#endif + } + if (buffer != nullptr) { + object = new (buffer) T(std::forward(args)...); + } + return object; +} + +template +inline T* MemAllocAlignedObject(uint32_t alignment, bool global, Args&&... args) +{ + T* object = nullptr; + void* buffer; + if (global) { + buffer = MemGlobalAllocAligned(sizeof(T), alignment); + } else { +#ifdef MEM_SESSION_ACTIVE + buffer = MemSessionAllocAligned(sizeof(T), alignment); +#else + buffer = memalign(alignment, sizeof(T)); +#endif + } + if (buffer != nullptr) { + object = new (buffer) T(std::forward(args)...); + } + return object; +} + +inline void MemFree(void* object, bool global) +{ + if (global) { + return MemGlobalFree(object); + } else { +#ifdef MEM_SESSION_ACTIVE + return MemSessionFree(object); +#else + return free(object); +#endif + } +} + +template +inline void MemFreeObject(T* object, bool global) +{ + if (object != nullptr) { + object->~T(); + MemFree((void*)object, global); + } +} }; // namespace MOT /** diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.cpp b/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.cpp index f60c892ca..8fe535aae 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.cpp @@ -54,8 +54,8 @@ extern int MemBufferAllocatorInit(MemBufferAllocator* bufferAllocator, int node, int result = 0; uint64_t chunkSnapshotSize = sizeof(MemBufferChunkSnapshot) * g_memGlobalCfg.m_maxThreadCount; // this is a bit too much, but code is simpler - uint64_t freeListArraySize = sizeof(MemBufferList) * - g_memGlobalCfg.m_maxThreadCount; // this is too much, but code is simpler + uint64_t freeListArraySize = + sizeof(MemBufferList) * g_memGlobalCfg.m_maxThreadCount; // this is too much, but code is simpler freeListArraySize = L1_ALIGN(freeListArraySize); // L1 Cache line aligned size @@ -78,6 +78,7 @@ extern int MemBufferAllocatorInit(MemBufferAllocator* bufferAllocator, int node, } else { // initialize all pointers bufferAllocator->m_bufferHeap = (MemBufferHeap*)inplaceBuffer; + MOT_ASSERT((uintptr_t)(bufferAllocator->m_bufferHeap) == L1_ALIGNED_PTR(bufferAllocator->m_bufferHeap)); bufferAllocator->m_chunkSnapshots = (MemBufferChunkSnapshot*)(bufferAllocator->m_bufferHeap + 1); MOT_ASSERT((uintptr_t)(bufferAllocator->m_chunkSnapshots) == L1_ALIGNED_PTR(bufferAllocator->m_chunkSnapshots)); bufferAllocator->m_freeListArray = @@ -179,7 +180,8 @@ extern MemBufferHeader* MemBufferChunkSnapshotAllocBuffer(MemBufferChunkSnapshot uint64_t freeBufferIndex = __builtin_clzll(chunkHeader->m_freeBitset[i]); chunkHeader->m_freeBitset[i] &= ~(((uint64_t)1) << (63 - freeBufferIndex)); ++chunkHeader->m_allocatedCount; - bufferHeader = MM_CHUNK_BUFFER_HEADER_AT(chunkSnapshot->m_realChunkHeader, (i << 6) + freeBufferIndex); + bufferHeader = + MM_CHUNK_BUFFER_HEADER_AT(chunkSnapshot->m_realChunkHeader, ((uint64_t)i << 6) + freeBufferIndex); break; } ++chunkSnapshot->m_bitsetIndex; @@ -212,7 +214,6 @@ static MemBufferChunkHeader* RefillGetChunkHeader( if (chunkHeader != NULL) { MOT_LOG_DEBUG("allocated chunk from pool"); MemBufferChunkInit(chunkHeader, - chunkHeader->m_node, bufferAllocator->m_bufferHeap->m_allocType, bufferAllocator->m_bufferHeap->m_node, bufferAllocator->m_bufferHeap->m_bufferClass); @@ -341,6 +342,51 @@ extern void MemBufferAllocatorFree(MemBufferAllocator* bufferAllocator, void* bu } } +extern int MemBufferAllocatorReserve(MemBufferAllocator* bufferAllocator, uint32_t chunkCount) +{ + // we calculate how much memory is required and then order our local heap to load chunks from the chunk pool + int result = 0; + MOTThreadId threadId = MOTCurrThreadId; + if (unlikely(threadId == INVALID_THREAD_ID)) { + MemBufferAllocatorIssueError(MOT_ERROR_INTERNAL, + "Reserve Memory", + "Invalid attempt to reserve memory without current thread identifier"); + result = MOT_ERROR_INTERNAL; + } else { + MemBufferChunkSnapshot* chunkSnapshot = &bufferAllocator->m_chunkSnapshots[threadId]; + if (chunkSnapshot != nullptr) { + MemBufferChunkHeader* chunkHeader = &chunkSnapshot->m_chunkHeaderSnapshot; + // in this case we already reserved chunk which was not used + // decrement number of required chunks + if (chunkHeader->m_allocatedCount == 0) { + --chunkCount; + } + } + if (chunkCount > 0) { + uint32_t bufferCount = chunkCount * bufferAllocator->m_bufferHeap->m_maxBuffersInChunk; + result = MemBufferHeapReserve(bufferAllocator->m_bufferHeap, bufferCount); + } + } + + return result; +} + +extern int MemBufferAllocatorUnreserve(MemBufferAllocator* bufferAllocator) +{ + int result = 0; + MOTThreadId threadId = MOTCurrThreadId; + if (unlikely(threadId == INVALID_THREAD_ID)) { + MemBufferAllocatorIssueError(MOT_ERROR_INTERNAL, + "Unreserve Memory", + "Invalid attempt to unreserve memory without current thread identifier"); + result = MOT_ERROR_INTERNAL; + } else { + result = MemBufferHeapUnreserve(bufferAllocator->m_bufferHeap); + } + + return result; +} + static void MemChunkSnapshotClearThreadCache(MemBufferAllocator* bufferAllocator, MOTThreadId threadId) { MOT_LOG_TRACE("Clearing chunk snapshot and free list for thread %u at buffer allocator [%s-%s]", @@ -500,10 +546,10 @@ extern void MemBufferAllocatorToString(int indent, const char* name, MemBufferAl MemBufferList* freeList = &bufferAllocator->m_freeListArray[i]; if (freeList->m_count > 0) { const uint32_t nameLen = 32; - char name[nameLen]; - erc = snprintf_s(name, nameLen, nameLen - 1, "TID %u Free", i); + char tmpName[nameLen]; + erc = snprintf_s(tmpName, nameLen, nameLen - 1, "TID %u Free", i); securec_check_ss(erc, "\0", "\0"); - MemBufferListToString(indent + PRINT_REPORT_INDENT, name, freeList, stringBuffer); + MemBufferListToString(indent + PRINT_REPORT_INDENT, tmpName, freeList, stringBuffer); StringBufferAppend(stringBuffer, "\n"); } } @@ -522,8 +568,8 @@ extern "C" void MemBufferAllocatorDump(void* arg) MOT::StringBufferApply([bufferAllocator](MOT::StringBuffer* stringBuffer) { MOT::MemBufferAllocatorToString(0, "Debug Dump", bufferAllocator, stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -539,7 +585,7 @@ extern "C" int MemBufferAllocatorAnalyze(void* allocator, void* buffer) MOT::MemBufferHeader* bufferHeader = MOT::MemBufferChunkGetBufferHeader(chunkHeader, buffer); if (bufferHeader) { bool isAllocated = MOT::MemBufferChunkIsAllocated(chunkHeader, bufferHeader); - fprintf(stderr, + (void)fprintf(stderr, "Found %s buffer %p in position %" PRIu64 " at chunk snapshot for thread %u of buffer allocator [node=%d, buffer-size=%s]\n", isAllocated ? "allocated" : "free", @@ -560,7 +606,7 @@ extern "C" int MemBufferAllocatorAnalyze(void* allocator, void* buffer) MOT::MemBufferHeader* itr = bufferAllocator->m_freeListArray[i].m_head; while (itr && !found) { if (itr->m_buffer == buffer) { - fprintf(stderr, + (void)fprintf(stderr, "Found buffer %p in free list for thread %u of buffer allocator [node=%d, buffer-size=%s]\n", buffer, i, diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.h b/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.h index 6fdbf648b..f5797fb74 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.h +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_allocator.h @@ -215,6 +215,23 @@ inline void* MemBufferAllocatorAlloc(MemBufferAllocator* bufferAllocator) */ extern void MemBufferAllocatorFree(MemBufferAllocator* bufferAllocator, void* buffer); +/** + * @brief Reserve memory for current session. While in reserve-mode, released chunks are kept in the current session's + * reserve, rather than being released to global memory. + * @param bufferAllocator The buffer allocator. + * @param chunkCount The number of chunks to reserve. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferAllocatorReserve(MemBufferAllocator* bufferAllocator, uint32_t chunkCount); + +/** + * @brief Release all global memory reserved for current session for a specific buffer class. + * @param bufferAllocator The buffer allocator. + * @param bufferClass The buffer class for which an existing reservation is to be released. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferAllocatorUnreserve(MemBufferAllocator* bufferAllocator); + /** * @brief Utility function for guarding against double free. * @param buffer The buffer to check. diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_api.cpp b/src/gausskernel/storage/mot/core/memory/mm_buffer_api.cpp index e9e463af5..5ea9f9238 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_api.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_api.cpp @@ -39,9 +39,9 @@ #include "session_context.h" #include "mm_api.h" -#include +#include #include -#include +#include namespace MOT { DECLARE_LOGGER(BufferApi, Memory) @@ -101,7 +101,7 @@ extern void MemBufferFreeGlobal(void* buffer, MemBufferClass bufferClass) // allocator node in a local variables to avoid core dump int allocatorNode = bufferChunkHeader->m_allocatorNode; MemBufferAllocatorFree(&g_globalAllocators[allocatorNode][bufferClass], buffer); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalBuffersFreed(allocatorNode, bufferClass); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalBuffersFreed(allocatorNode, bufferClass); } else { MOT_LOG_PANIC("Attempt to release invalid buffer at %p: source chunk not found", buffer); @@ -138,6 +138,36 @@ extern void MemBufferClearSessionCache() } } +extern int MemBufferReserveGlobal(uint32_t chunkCount) +{ + int result = 0; + int node = MOTCurrentNumaNodeId; + if (node < 0) { + MemBufferIssueError(MOT_ERROR_INVALID_ARG, + "Cannot reserve %u chunks from global memory: Invalid NUMA node identifier %u", + chunkCount, + node); + result = MOT_ERROR_INVALID_ARG; + } else { + result = MemRawChunkStoreReserveGlobal(node, chunkCount); + } + return result; +} + +extern int MemBufferUnreserveGlobal(uint32_t chunkCount) +{ + int result = 0; + int node = MOTCurrentNumaNodeId; + if (node < 0) { + MemBufferIssueError( + MOT_ERROR_INVALID_ARG, "Cannot unreserve global memory: Invalid NUMA node identifier %u", node); + result = MOT_ERROR_INVALID_ARG; + } else { + MemRawChunkStoreUnreserveGlobal(node, chunkCount); + } + return result; +} + extern void MemBufferApiPrint(const char* name, LogLevel logLevel, MemReportMode reportMode /* = MEM_REPORT_SUMMARY */) { if (MOT_CHECK_LOG_LEVEL(logLevel)) { @@ -339,8 +369,8 @@ extern "C" void MemBufferApiDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemBufferApiToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -348,8 +378,8 @@ extern "C" int MemBufferApiAnalyze(void* buffer) { for (uint32_t i = 0; i < MOT::g_memGlobalCfg.m_nodeCount; ++i) { for (MOT::MemBufferClass bc = MOT::MEM_BUFFER_CLASS_KB_1; bc < MOT::MEM_BUFFER_CLASS_LARGEST; ++bc) { - fprintf(stderr, - "Searching buffer %p in %s global-%d buffer allocator...\n", + (void)fprintf(stderr, + "Searching buffer %p in %s global-%u buffer allocator...\n", buffer, MOT::MemBufferClassToString(bc), i); @@ -360,8 +390,8 @@ extern "C" int MemBufferApiAnalyze(void* buffer) } for (uint32_t i = 0; i < MOT::g_memGlobalCfg.m_nodeCount; ++i) { for (MOT::MemBufferClass bc = MOT::MEM_BUFFER_CLASS_KB_1; bc < MOT::MEM_BUFFER_CLASS_LARGEST; ++bc) { - fprintf(stderr, - "Searching buffer %p in %s local-%d buffer allocator...\n", + (void)fprintf(stderr, + "Searching buffer %p in %s local-%u buffer allocator...\n", buffer, MOT::MemBufferClassToString(bc), i); diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_api.h b/src/gausskernel/storage/mot/core/memory/mm_buffer_api.h index f6613883d..cf6a7b79b 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_api.h +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_api.h @@ -36,7 +36,7 @@ #include "mm_cfg.h" #include "mot_error.h" -#include +#include namespace MOT { /** @var Array of global (long-term) buffer allocators per NUMA node. */ @@ -85,7 +85,7 @@ inline void* MemBufferAllocGlobal(MemBufferClass bufferClass) if (buffer == nullptr) { // this is a rare out of memory scenario - all chunk pools are depleted, but other allocators might still // have memory - for (uint32_t i = (node + 1) % g_memGlobalCfg.m_nodeCount; i != (uint32_t)node; + for (uint32_t i = ((uint32_t)node + 1) % g_memGlobalCfg.m_nodeCount; i != (uint32_t)node; i = (i + 1) % g_memGlobalCfg.m_nodeCount) { buffer = MemBufferAllocatorAlloc(&g_globalAllocators[i][bufferClass]); if (buffer != nullptr) { @@ -94,7 +94,7 @@ inline void* MemBufferAllocGlobal(MemBufferClass bufferClass) } } if (buffer != nullptr) { - DetailedMemoryStatisticsProvider::m_provider->AddGlobalBuffersUsed(node, bufferClass); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalBuffersUsed(node, bufferClass); } else { MemBufferIssueError(MOT_ERROR_OOM, "Failed to allocate %s global buffer: out of memory", @@ -122,7 +122,7 @@ inline void* MemBufferAllocOnNode(MemBufferClass bufferClass, int node) } else { buffer = MemBufferAllocatorAlloc(&g_localAllocators[node][bufferClass]); if (buffer != nullptr) { - DetailedMemoryStatisticsProvider::m_provider->AddLocalBuffersUsed(node, bufferClass); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalBuffersUsed(node, bufferClass); } else { MemBufferIssueError(MOT_ERROR_OOM, "Failed to allocate %s local buffer on node %d: out of memory", @@ -163,7 +163,7 @@ inline void MemBufferFreeOnNode(void* buffer, MemBufferClass bufferClass, int no MemBufferIssueError(MOT_ERROR_INVALID_ARG, "Cannot free %s local buffer on node %d", node); } else { MemBufferAllocatorFree(&g_localAllocators[node][bufferClass], buffer); - DetailedMemoryStatisticsProvider::m_provider->AddLocalBuffersFreed(node, bufferClass); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalBuffersFreed(node, bufferClass); } } @@ -179,6 +179,66 @@ extern void MemBufferFreeLocal(void* buffer, MemBufferClass bufferClass); */ extern void MemBufferClearSessionCache(); +/** + * @brief Reserve global memory for current session. While in reserve-mode, released chunks are kept in the current + * session's reserve, rather than being released to global memory. + * @param chunkCount The number of chunks to reserve. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferReserveGlobal(uint32_t chunkCount); + +/** + * @brief Release all global memory reserved for current session. + * @param bufferClass The buffer class for which an existing reservation is to be released. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferUnreserveGlobal(uint32_t chunkCount); + +/** + * @brief Reserve global memory for current session for a specific buffer class. While in reserve-mode, released chunks + * are kept in the current session's reserve, rather than being released to global memory. + * @param bufferClass The buffer class for which reservation is to be made. + * @param chunkCount The number of chunks to reserve. + * @return Zero on success, otherwise error code on failure. + */ +inline int MemBufferReserveGlobal(MemBufferClass bufferClass, uint32_t chunkCount) +{ + int result = 0; + int node = MOTCurrentNumaNodeId; + if (node < 0) { + MemBufferIssueError(MOT_ERROR_INVALID_ARG, + "Cannot reserve %u chunks for %s global buffers: Invalid NUMA node identifier %u", + chunkCount, + MemBufferClassToString(bufferClass), + node); + result = MOT_ERROR_INVALID_ARG; + } else { + result = MemBufferAllocatorReserve(&g_localAllocators[node][bufferClass], chunkCount); + } + return result; +} + +/** + * @brief Release all global memory reserved for current session for a specific buffer class. + * @param bufferClass The buffer class for which an existing reservation is to be released. + * @return Zero on success, otherwise error code on failure. + */ +inline int MemBufferUnreserveGlobal(MemBufferClass bufferClass) +{ + int result = 0; + int node = MOTCurrentNumaNodeId; + if (node < 0) { + MemBufferIssueError(MOT_ERROR_INVALID_ARG, + "Cannot unreserve global memory for %s buffers: Invalid NUMA node identifier %u", + MemBufferClassToString(bufferClass), + node); + result = MOT_ERROR_INVALID_ARG; + } else { + result = MemBufferAllocatorUnreserve(&g_localAllocators[node][bufferClass]); + } + return result; +} + /** * @brief Prints all buffer API status into log. * @param name The name to prepend to the log message. diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.cpp b/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.cpp index 8fd0a1902..fdc0fed51 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.cpp @@ -32,7 +32,7 @@ #include "mm_def.h" #include "mm_api.h" -#include +#include namespace MOT { DECLARE_LOGGER(BufferChunk, Memory) @@ -49,7 +49,7 @@ static uint32_t CalcMaxBuffers(uint32_t bufferSizeKb, uint64_t* paddedSizeOut) // now we calculate the padded size required for all headers (rounded up to the next page boundary) uint32_t fullHeaderSize = sizeof(MemBufferChunkHeader) + bufferCount * sizeof(MemBufferHeader); - uint64_t paddedSize = (fullHeaderSize + PAGE_SIZE_BYTES - 1) / PAGE_SIZE_BYTES * PAGE_SIZE_BYTES; + uint64_t paddedSize = ((fullHeaderSize + PAGE_SIZE_BYTES - 1) / PAGE_SIZE_BYTES) * PAGE_SIZE_BYTES; if (paddedSizeOut != nullptr) { *paddedSizeOut = paddedSize; } @@ -85,11 +85,10 @@ extern void MemBufferChunkReportError(int errorCode, const char* format, ...) va_end(args); } -extern void MemBufferChunkInit(MemBufferChunkHeader* chunkHeader, int16_t node, MemAllocType allocType, - int16_t allocatorNode, MemBufferClass bufferClass) +extern void MemBufferChunkInit( + MemBufferChunkHeader* chunkHeader, MemAllocType allocType, int16_t allocatorNode, MemBufferClass bufferClass) { chunkHeader->m_chunkType = MEM_CHUNK_TYPE_BUFFER; - chunkHeader->m_node = node; chunkHeader->m_allocType = allocType; chunkHeader->m_allocatorNode = allocatorNode; chunkHeader->m_bufferClass = bufferClass; @@ -190,7 +189,7 @@ extern "C" void MemBufferHeaderDump(void* arg) { MOT::MemBufferHeader* bufferHeader = (MOT::MemBufferHeader*)arg; MOT::MemBufferChunkHeader* chunkHeader = (MOT::MemBufferChunkHeader*)MOT::MemRawChunkDirLookup(bufferHeader); - fprintf(stderr, + (void)fprintf(stderr, "Buffer Header %u @ %p --> %p (source chunk: %p)\n", (unsigned)bufferHeader->m_index, bufferHeader, @@ -203,8 +202,8 @@ extern "C" void MemBufferChunkHeaderDump(void* arg) MOT::MemBufferChunkHeader* chunkHeader = (MOT::MemBufferChunkHeader*)arg; MOT::StringBufferApply([chunkHeader](MOT::StringBuffer* stringBuffer) { MOT::MemBufferChunkHeaderToString(chunkHeader, stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -214,14 +213,13 @@ extern "C" void MemBufferChunkListDump(void* arg) MOT::StringBufferApply([chunkList](MOT::StringBuffer* stringBuffer) { MOT::MemBufferChunkListToString(0, "Debug Dump", chunkList, stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } extern "C" void MemBufferDump(void* arg) { - MOT::StringBufferApply([arg](MOT::StringBuffer* stringBuffer) { StringBufferAppend(stringBuffer, "Analysis report for memory buffer %p:\n", arg); MOT::MemBufferChunkHeader* chunkHeader = (MOT::MemBufferChunkHeader*)MOT::MemRawChunkDirLookup(arg); @@ -238,7 +236,7 @@ extern "C" void MemBufferDump(void* arg) } else { StringBufferAppend(stringBuffer, " buffer not found"); } - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.h b/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.h index 20f3c14fe..ecf15bd53 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.h +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_chunk.h @@ -33,7 +33,7 @@ #include "mm_raw_chunk_dir.h" #include "string_buffer.h" -#include +#include // API for chunk allocation // Chunk layout is as follows: @@ -142,8 +142,8 @@ extern void MemBufferChunkReportError(int errorCode, const char* format, ...); * @param allocatorNode The allocator node with which the chunk is associated (source allocator). * @param bufferClass The class of buffers managed by the chunk. */ -extern void MemBufferChunkInit(MemBufferChunkHeader* chunkHeader, int16_t node, MemAllocType allocType, - int16_t allocatorNode, MemBufferClass bufferClass); +extern void MemBufferChunkInit( + MemBufferChunkHeader* chunkHeader, MemAllocType allocType, int16_t allocatorNode, MemBufferClass bufferClass); /** * @brief Allocates a buffer from a chunk @@ -197,7 +197,7 @@ inline MemBufferHeader* MemBufferChunkGetBufferHeader(MemBufferChunkHeader* chun * @param buffer The buffer to check. * @return Non-zero value if the buffer is allocated, otherwise zero. */ -inline int MemBufferChunkIsAllocated(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader); +inline bool MemBufferChunkIsAllocated(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader); /** * @brief Queries whether a buffer is free. @@ -205,7 +205,7 @@ inline int MemBufferChunkIsAllocated(MemBufferChunkHeader* chunkHeader, MemBuffe * @param buffer The buffer to check. * @return Non-zero value if the buffer is free, otherwise zero. */ -inline int MemBufferChunkIsFree(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader); +inline bool MemBufferChunkIsFree(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader); /** * @brief Handles double buffer free. Dumps all relevant information and aborts. @@ -265,7 +265,7 @@ inline MemBufferHeader* MemBufferChunkAllocBuffer(MemBufferChunkHeader* chunkHea for (uint32_t i = 0; i < chunkHeader->m_freeBitsetCount; ++i) { if (chunkHeader->m_freeBitset[i] != 0) { uint64_t bitIndex = __builtin_clzll(chunkHeader->m_freeBitset[i]); - uint64_t bufferIndex = (i << 6) + bitIndex; + uint64_t bufferIndex = ((uint64_t)i << 6) + bitIndex; if (bufferIndex < chunkHeader->m_bufferCount) { chunkHeader->m_freeBitset[i] &= ~(((uint64_t)1) << (63 - bitIndex)); ++chunkHeader->m_allocatedCount; @@ -355,18 +355,18 @@ inline MemBufferHeader* MemBufferChunkGetBufferHeader(MemBufferChunkHeader* chun return bufferHeader; } -inline int MemBufferChunkIsAllocated(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader) +inline bool MemBufferChunkIsAllocated(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader) { return !MemBufferChunkIsFree(chunkHeader, bufferHeader); } -inline int MemBufferChunkIsFree(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader) +inline bool MemBufferChunkIsFree(MemBufferChunkHeader* chunkHeader, MemBufferHeader* bufferHeader) { - int result = 0; + bool result = false; uint32_t slot = bufferHeader->m_index / 64; uint32_t index = bufferHeader->m_index % 64; if (chunkHeader->m_freeBitset[slot] & (((uint64_t)1) << (63 - index))) { - result = 1; + result = true; } return result; } diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.cpp b/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.cpp index b5edd7980..63e7f3ab3 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.cpp @@ -34,12 +34,12 @@ namespace MOT { DECLARE_LOGGER(BufferHeap, Memory) // fullness bit-set utility macros -#define CHUNK_FULLNESS_INDEX(chunkHeader) ((chunkHeader)->m_bufferCount - (chunkHeader)->m_allocatedCount - 1) +#define CHUNK_FULLNESS_INDEX(chunkHeader) (((chunkHeader)->m_bufferCount - (chunkHeader)->m_allocatedCount) - 1) #define FULLNESS_BIT(fullnessOffset) (((uint64_t)1) << (63 - (fullnessOffset))) #define RAISE_FULLNESS_BIT(bufferHeap, fullnessIndex, fullnessOffset) \ - (bufferHeap)->m_fullnessBitset[fullnessIndex] |= FULLNESS_BIT(fullnessOffset) + ((bufferHeap)->m_fullnessBitset[fullnessIndex] |= FULLNESS_BIT(fullnessOffset)) #define RESET_FULLNESS_BIT(bufferHeap, fullnessIndex, fullnessOffset) \ - (bufferHeap)->m_fullnessBitset[fullnessIndex] &= ~FULLNESS_BIT(fullnessOffset) + ((bufferHeap)->m_fullnessBitset[fullnessIndex] &= ~FULLNESS_BIT(fullnessOffset)) // helpers static inline int GetNonEmptyFullnessIndex(MemBufferHeap* bufferHeap); @@ -57,6 +57,7 @@ static inline void MemChunkListUnlink(MemBufferChunkHeader** chunkList, MemBuffe static inline void MemChunkListFree(MemBufferHeap* bufferHeap, MemBufferChunkHeader* chunkList); static void AssertHeapValid(MemBufferHeap* bufferHeap); static void AssertChunkRemoved(MemBufferHeap* bufferHeap, MemBufferChunkHeader* chunkHeader); +static int AllocAndPushChunk(MemBufferHeap* bufferHeap); #ifdef MOT_DEBUG #define ASSERT_HEAP_VALID AssertHeapValid @@ -128,7 +129,8 @@ extern void MemBufferHeapDestroy(MemBufferHeap* bufferHeap) bufferHeap->m_allocatedChunkCount, bufferHeap->m_allocatedBufferCount); - MemLockDestroy(&bufferHeap->m_lock); // error ignored, it is already reported, and we have nothing to do about it + // error ignored, it is already reported, and we have nothing to do about it + (void)MemLockDestroy(&bufferHeap->m_lock); } extern MemBufferHeader* MemBufferHeapAlloc(MemBufferHeap* bufferHeap) @@ -140,7 +142,7 @@ extern MemBufferHeader* MemBufferHeapAlloc(MemBufferHeap* bufferHeap) int fullnessIndex = EnsureChunkExists(bufferHeap); if (fullnessIndex != -1) { // need to check, could be out of memory // unlink first first chunk in fullest chunk list - uint64_t fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[fullnessIndex]); + int fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[fullnessIndex]); MemBufferChunkHeader* chunkHeader = MemBufferHeapPopChunk(bufferHeap, fullnessIndex, fullnessOffset); // get buffer from chunk @@ -153,7 +155,7 @@ extern MemBufferHeader* MemBufferHeapAlloc(MemBufferHeap* bufferHeap) ++bufferHeap->m_allocatedBufferCount; // link modified chunk head to its new fullness list - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); } MemLockRelease(&bufferHeap->m_lock); @@ -178,7 +180,8 @@ extern void MemBufferHeapFree(MemBufferHeap* bufferHeap, MemBufferHeader* buffer --bufferHeap->m_allocatedBufferCount; // push chunk on its fullness list head - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + int allowFreeChunk = bufferHeap->m_inReserveMode ? 0 : 1; + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, allowFreeChunk); MemLockRelease(&bufferHeap->m_lock); } @@ -193,7 +196,7 @@ extern uint32_t MemBufferHeapAllocMultiple(MemBufferHeap* bufferHeap, uint32_t i int fullnessIndex = EnsureChunkExists(bufferHeap); if (fullnessIndex != -1) { // unlink first first chunk in fullest chunk list - uint64_t fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[fullnessIndex]); + int fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[fullnessIndex]); MemBufferChunkHeader* chunkHeader = MemBufferHeapPopChunk(bufferHeap, fullnessIndex, fullnessOffset); // get buffer from chunk @@ -207,7 +210,7 @@ extern uint32_t MemBufferHeapAllocMultiple(MemBufferHeap* bufferHeap, uint32_t i bufferHeap->m_allocatedBufferCount += buffersAllocatedCurr; // link modified chunk head to its new fullness list - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); } else { break; // out of memory } @@ -227,7 +230,7 @@ extern MemBufferChunkHeader* MemBufferHeapExtractChunk(MemBufferHeap* bufferHeap // get fullest fit if any int nonEmptyIndex = GetNonEmptyFullnessIndex(bufferHeap); if (nonEmptyIndex != -1) { - uint64_t fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[nonEmptyIndex]); + int fullnessOffset = __builtin_clzll(bufferHeap->m_fullnessBitset[nonEmptyIndex]); chunkHeader = MemBufferHeapPopChunk(bufferHeap, nonEmptyIndex, fullnessOffset); MOT_ASSERT(chunkHeader); MOT_ASSERT(chunkHeader->m_allocatedCount < chunkHeader->m_bufferCount); @@ -249,7 +252,8 @@ extern void MemBufferHeapRelinkChunk( ASSERT_HEAP_VALID(bufferHeap); // push chunk on its fullness list head - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + int allowFreeChunk = bufferHeap->m_inReserveMode ? 0 : 1; + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, allowFreeChunk); bufferHeap->m_allocatedBufferCount += buffersAllocated; if (newChunk) { ++bufferHeap->m_allocatedChunkCount; @@ -270,7 +274,7 @@ extern MemBufferChunkHeader* MemBufferHeapSnapshotChunk(MemBufferHeap* bufferHea if (nonEmptyIndex != -1) { // attention: we call ctz and **NOT** clz (trailing zeroes, not leading zeroes) since we search in // reverse order for the emptiest non-empty chunk list - uint64_t fullnessOffset = __builtin_ctzll(bufferHeap->m_fullnessBitset[nonEmptyIndex]); + int fullnessOffset = __builtin_ctzll(bufferHeap->m_fullnessBitset[nonEmptyIndex]); chunkHeader = MemBufferHeapPopChunk(bufferHeap, nonEmptyIndex, 63 - fullnessOffset); MOT_ASSERT(chunkHeader); MOT_ASSERT(chunkHeader->m_allocatedCount < chunkHeader->m_bufferCount); @@ -280,7 +284,7 @@ extern MemBufferChunkHeader* MemBufferHeapSnapshotChunk(MemBufferHeap* bufferHea errno_t erc = memcpy_s(chunkSnapshot, sizeof(MemBufferChunkHeader), chunkHeader, sizeof(MemBufferChunkHeader)); securec_check(erc, "\0", "\0"); MemBufferChunkMarkFull(chunkHeader); - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 0); + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, 0); // update statistics bufferHeap->m_allocatedBufferCount += chunkSnapshot->m_bufferCount - chunkSnapshot->m_allocatedCount; @@ -314,13 +318,70 @@ extern void MemBufferHeapFreeMultiple(MemBufferHeap* bufferHeap, MemBufferList* --bufferHeap->m_allocatedBufferCount; // push chunk on its fullness list head - MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + int allowFreeChunk = bufferHeap->m_inReserveMode ? 0 : 1; + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, allowFreeChunk); } ASSERT_HEAP_VALID(bufferHeap); MemLockRelease(&bufferHeap->m_lock); } +extern int MemBufferHeapReserve(MemBufferHeap* bufferHeap, uint32_t bufferCount) +{ + int result = 0; + MemLockAcquire(&bufferHeap->m_lock); + + // raise reserve mode flag + bufferHeap->m_inReserveMode = 1; + + // count number of available buffers + uint32_t availableBuffers = 0; + for (uint32_t i = 0; i < bufferHeap->m_maxBuffersInChunk; ++i) { + MemBufferChunkHeader* chunkHeader = bufferHeap->m_fullnessDirectory[i]; + while (chunkHeader != nullptr) { + availableBuffers += (chunkHeader->m_bufferCount - chunkHeader->m_allocatedCount); + chunkHeader = chunkHeader->m_nextChunk; + } + } + + if (availableBuffers < bufferCount) { + // compute number of required chunks (round up) + uint32_t requiredBuffers = bufferCount - availableBuffers; + uint32_t requiredBytes = + (requiredBuffers + bufferHeap->m_maxBuffersInChunk - 1) * bufferHeap->m_bufferSizeKb * KILO_BYTE; + uint32_t requiredChunks = requiredBytes / (MEM_CHUNK_SIZE_MB * MEGA_BYTE); + for (uint32_t i = 0; i < requiredChunks; ++i) { + result = AllocAndPushChunk(bufferHeap); + if (result == -1) { + result = MOT_ERROR_OOM; + bufferHeap->m_inReserveMode = 0; + break; + } + } + } + + MemLockRelease(&bufferHeap->m_lock); + return result; +} + +extern int MemBufferHeapUnreserve(MemBufferHeap* bufferHeap) +{ + // just reset reserve flag and let things happen naturally + MemLockAcquire(&bufferHeap->m_lock); + bufferHeap->m_inReserveMode = 0; + + // free all empty chunks in compact mode + if (g_memGlobalCfg.m_storeMemoryPolicy == MEM_STORE_COMPACT) { + while (bufferHeap->m_fullnessDirectory[bufferHeap->m_maxBuffersInChunk - 1] != nullptr) { + MemBufferChunkHeader* chunkHeader = bufferHeap->m_fullnessDirectory[bufferHeap->m_maxBuffersInChunk - 1]; + MemBufferHeapUnlinkChunk(bufferHeap, chunkHeader); + (void)MemBufferHeapPushChunk(bufferHeap, chunkHeader, 1); + } + } + MemLockRelease(&bufferHeap->m_lock); + return 0; +} + extern void MemBufferHeapPrint(const char* name, LogLevel logLevel, MemBufferHeap* bufferHeap) { if (MOT_CHECK_LOG_LEVEL(logLevel)) { @@ -353,11 +414,11 @@ extern void MemBufferHeapToString(int indent, const char* name, MemBufferHeap* b StringBufferAppend(stringBuffer, "\n%*sFullness Chunk Lists =\n", indent + PRINT_REPORT_INDENT, ""); for (uint32_t i = 0; i < bufferHeap->m_maxBuffersInChunk; ++i) { if (bufferHeap->m_fullnessDirectory[i] != NULL) { - char name[20]; - erc = snprintf_s(name, sizeof(name), sizeof(name) - 1, "Fullness[%u]", i); + char tmpName[20]; + erc = snprintf_s(tmpName, sizeof(tmpName), sizeof(tmpName) - 1, "Fullness[%u]", i); securec_check_ss(erc, "\0", "\0"); MemBufferChunkListToString( - indent + (2 * PRINT_REPORT_INDENT), name, bufferHeap->m_fullnessDirectory[i], stringBuffer); + indent + (2 * PRINT_REPORT_INDENT), tmpName, bufferHeap->m_fullnessDirectory[i], stringBuffer); StringBufferAppend(stringBuffer, "\n"); } } @@ -394,22 +455,25 @@ static inline int EnsureChunkExists(MemBufferHeap* bufferHeap) { int nonEmptyIndex = GetNonEmptyFullnessIndex(bufferHeap); if (nonEmptyIndex == -1) { - // allocate one chunk and update bit set (very costly unless preallocated in background) - MemBufferChunkHeader* chunkHeader = NULL; - if (bufferHeap->m_allocType == MEM_ALLOC_GLOBAL) { - chunkHeader = (MemBufferChunkHeader*)MemRawChunkStoreAllocGlobal(bufferHeap->m_node); - } else { - chunkHeader = (MemBufferChunkHeader*)MemRawChunkStoreAllocLocal(bufferHeap->m_node); - } - if (chunkHeader != NULL) { - MemBufferChunkInit(chunkHeader, - chunkHeader->m_node, - bufferHeap->m_allocType, - bufferHeap->m_node, - bufferHeap->m_bufferClass); - ++bufferHeap->m_allocatedChunkCount; - nonEmptyIndex = MemBufferHeapPushChunk(bufferHeap, chunkHeader, 0); - } + nonEmptyIndex = AllocAndPushChunk(bufferHeap); + } + return nonEmptyIndex; +} + +static int AllocAndPushChunk(MemBufferHeap* bufferHeap) +{ + // allocate one chunk and update bit set (very costly unless preallocated in background) + int nonEmptyIndex = -1; + MemBufferChunkHeader* chunkHeader = NULL; + if (bufferHeap->m_allocType == MEM_ALLOC_GLOBAL) { + chunkHeader = (MemBufferChunkHeader*)MemRawChunkStoreAllocGlobal(bufferHeap->m_node); + } else { + chunkHeader = (MemBufferChunkHeader*)MemRawChunkStoreAllocLocal(bufferHeap->m_node); + } + if (chunkHeader != NULL) { + MemBufferChunkInit(chunkHeader, bufferHeap->m_allocType, bufferHeap->m_node, bufferHeap->m_bufferClass); + ++bufferHeap->m_allocatedChunkCount; + nonEmptyIndex = MemBufferHeapPushChunk(bufferHeap, chunkHeader, 0); } return nonEmptyIndex; } @@ -419,7 +483,7 @@ static inline MemBufferChunkHeader* MemBufferHeapPopChunk( MemBufferHeap* bufferHeap, int fullnessIndex, int fullnessOffset) { // pop chunk from list - int chunkListIndex = (fullnessIndex << 6) + fullnessOffset; + uint32_t chunkListIndex = (static_cast(fullnessIndex) << 6) + fullnessOffset; MemBufferChunkHeader* chunkHeader = MemChunkListPop(&bufferHeap->m_fullnessDirectory[chunkListIndex]); MOT_ASSERT(chunkHeader); @@ -502,7 +566,7 @@ static inline void MemChunkListUnlink(MemBufferChunkHeader** chunkList, MemBuffe { // separate cases if (*chunkList == chunkHeader) { // unlink head - MemChunkListPop(chunkList); + (void)MemChunkListPop(chunkList); } else { if (chunkHeader->m_prevChunk != nullptr) { chunkHeader->m_prevChunk->m_nextChunk = chunkHeader->m_nextChunk; @@ -573,7 +637,7 @@ extern "C" void MemBufferHeapDump(void* arg) MOT::StringBufferApply([bufferHeap](MOT::StringBuffer* stringBuffer) { MOT::MemBufferHeapToString(0, "Debug Dump", bufferHeap, stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.h b/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.h index 3a2add85e..e4329f8e1 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.h +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_heap.h @@ -51,8 +51,11 @@ struct MemBufferHeap { /** @var The size of class of buffers managed by this heap. */ MemBufferClass m_bufferClass; // L1 offset [8-9] + /** @var Specifies whether the heap is in reserve mode. */ + uint8_t m_inReserveMode; // L1 offset [9-10] + /** @var Pad next member to 4 bytes alignment. */ - uint8_t m_padding2[3]; // L1 offset [9-12] + uint8_t m_padding2[2]; // L1 offset [10-12] /** @var The number of buffers in each chunk in this heap. */ uint32_t m_maxBuffersInChunk; // L1 offset [12-16] @@ -91,14 +94,17 @@ struct MemBufferHeap { /** @var List of all full chunks. */ MemBufferChunkHeader* m_fullChunkList; // L1 offset [0-8] + /** @var List of reserved chunks. */ + MemBufferChunkHeader* m_reserveChunkList; // L1 offset [8-16] + /** @var The number of chunks currently allocated by this heap. */ - uint64_t m_allocatedChunkCount; // L1 offset [8-16] + uint64_t m_allocatedChunkCount; // L1 offset [16-24] /** @var The number of buffers currently allocated by this heap. */ - uint64_t m_allocatedBufferCount; // L1 offset [16-24] + uint64_t m_allocatedBufferCount; // L1 offset [24-32] /** @var Padding to round off the size to L1 cache line aligned size. */ - uint8_t m_padding5[40]; // L1 offset [24-64] + uint8_t m_padding5[32]; // L1 offset [32-64] }; static_assert(sizeof(MemBufferHeap) == L1_ALIGNED_SIZEOF(MemBufferHeap), @@ -177,6 +183,23 @@ extern MemBufferChunkHeader* MemBufferHeapSnapshotChunk(MemBufferHeap* bufferHea */ extern void MemBufferHeapFreeMultiple(MemBufferHeap* bufferHeap, MemBufferList* bufferList); +/** + * @brief Reserve memory for current session. While in reserve-mode, released chunks are kept in the current session's + * reserve, rather than being released to global memory. + * @param bufferHeap The buffer heap. + * @param bufferCount The number of buffers to reserve. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferHeapReserve(MemBufferHeap* bufferHeap, uint32_t bufferCount); + +/** + * @brief Release all global memory reserved for current session for a specific buffer class. + * @param bufferHeap The buffer heap. + * @param bufferClass The buffer class for which an existing reservation is to be released. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemBufferHeapUnreserve(MemBufferHeap* bufferHeap); + /** * @brief Prints a buffer heap into log. * @param name The name of the buffer heap to print. diff --git a/src/gausskernel/storage/mot/core/memory/mm_buffer_list.cpp b/src/gausskernel/storage/mot/core/memory/mm_buffer_list.cpp index 90f5c058c..8e2437c47 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_buffer_list.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_buffer_list.cpp @@ -58,7 +58,7 @@ extern "C" void MemBufferListDump(void* arg) MOT::StringBufferApply([bufferList](MOT::StringBuffer* stringBuffer) { MOT::MemBufferListToString(0, "Debug Dump", bufferList, stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_cfg.cpp b/src/gausskernel/storage/mot/core/memory/mm_cfg.cpp index 470858bbb..a7b0bf451 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_cfg.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_cfg.cpp @@ -39,7 +39,7 @@ namespace MOT { DECLARE_LOGGER(MemCfg, Memory) MemCfg g_memGlobalCfg; -static int memCfgInitOnce = 0; // initialize only once +static bool g_memCfgInitOnce = false; // initialize only once static uint64_t totalMemoryMb = 0; static uint64_t availableMemoryMb = 0; @@ -48,10 +48,10 @@ static int ValidateCfg(); extern int MemCfgInit() { - if (memCfgInitOnce) { + if (g_memCfgInitOnce) { return 0; // already initialized successfully } - memCfgInitOnce = 1; + g_memCfgInitOnce = true; MOT_LOG_TRACE("Loading MM configuration"); int res = InitTotalAvailMemory(); @@ -68,7 +68,7 @@ extern int MemCfgInit() g_memGlobalCfg.m_maxConnectionCount = motCfg.m_maxConnections; g_memGlobalCfg.m_maxThreadsPerNode = g_memGlobalCfg.m_maxThreadCount / g_memGlobalCfg.m_nodeCount; - g_memGlobalCfg.m_lazyLoadChunkDirectory = motCfg.m_lazyLoadChunkDirectory; + g_memGlobalCfg.m_lazyLoadChunkDirectory = static_cast(motCfg.m_lazyLoadChunkDirectory); g_memGlobalCfg.m_maxGlobalMemoryMb = motCfg.m_globalMemoryMaxLimitMB; g_memGlobalCfg.m_minGlobalMemoryMb = motCfg.m_globalMemoryMinLimitMB; @@ -126,7 +126,7 @@ extern int MemCfgInit() extern void MemCfgDestroy() { // currently nothing else than reset initialized flag - memCfgInitOnce = false; + g_memCfgInitOnce = false; } extern void MemCfgPrint(const char* name, LogLevel logLevel) @@ -247,6 +247,33 @@ static int InitTotalAvailMemory() return result; } +static int ComputeValidMaxObjectSizeMB(uint64_t& maxObjectSizeMB) +{ + // NOTE: we arrive here since the configured max object size is invalid (either zero or too high) + // the maximum object size cannot exceed 1/8 of the store size + uint64_t maxObjectSizeMBUpperBound = + g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR; + MOT_LOG_TRACE("Max object size upper bound is %" PRIu64 " MB", maxObjectSizeMBUpperBound); + + // the maximum size must be a power of two + maxObjectSizeMB = ComputeNearestLowPow2(maxObjectSizeMBUpperBound); + MOT_LOG_TRACE("Computed valid max object size: %" PRIu64 " MB", maxObjectSizeMB); + + // check for any computation error + if ((maxObjectSizeMB < MOTConfiguration::MIN_SESSION_LARGE_BUFFER_STORE_MAX_OBJECT_SIZE_MB * MEGA_BYTE) || + (maxObjectSizeMB > MOTConfiguration::MAX_SESSION_LARGE_BUFFER_STORE_MAX_OBJECT_SIZE_MB * MEGA_BYTE)) { + // we print and return error because this is an internal bug + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "MM Layer Configuration Validation", + "Maximum computed object size %" PRIu64 " exceeds allowed bounds [%" PRIu64 " MB, %" PRIu64 " MB]", + maxObjectSizeMB, + MOTConfiguration::MIN_SESSION_LARGE_BUFFER_STORE_MAX_OBJECT_SIZE_MB, + MOTConfiguration::MAX_SESSION_LARGE_BUFFER_STORE_MAX_OBJECT_SIZE_MB); + return MOT_ERROR_INTERNAL; + } + return MOT_NO_ERROR; +} + static int ValidateCfg() { // check node count limit @@ -337,21 +364,29 @@ static int ValidateCfg() // validate maximum large object size (issue warning and rectify) if (g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb > g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR) { - MOT_LOG_WARN("Invalid memory configuration: maximum object size %u MB in large buffer store is too big, value " - "will be truncated to %u MB.", + uint64_t maxObjectSizeMB = 0; + int result = ComputeValidMaxObjectSizeMB(maxObjectSizeMB); + if (result != MOT_NO_ERROR) { + return result; + } + MOT_LOG_WARN("Invalid memory configuration: maximum object size %" PRIu64 + " MB in large buffer store is too big, value will be truncated to %" PRIu64 " MB.", g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb, - g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR); - g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb = - g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR; + maxObjectSizeMB); + g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb = maxObjectSizeMB; } if ((g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb == 0) && (g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb > 0)) { + uint64_t maxObjectSizeMB = 0; + int result = ComputeValidMaxObjectSizeMB(maxObjectSizeMB); + if (result != MOT_NO_ERROR) { + return result; + } MOT_LOG_WARN("Invalid memory configuration: maximum object size in large buffer store is zero, value will be " - "rectified to %u MB.", - g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR); - g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb = - g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb / MEM_SESSION_MAX_LARGE_OBJECT_FACTOR; + "rectified to %" PRIu64 " MB.", + maxObjectSizeMB); + g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb = maxObjectSizeMB; } return 0; @@ -362,7 +397,7 @@ extern "C" void MemCfgDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemCfgToString(0, "Debug Dump", stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_chunk_type.h b/src/gausskernel/storage/mot/core/memory/mm_chunk_type.h index a129a177c..229e48aec 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_chunk_type.h +++ b/src/gausskernel/storage/mot/core/memory/mm_chunk_type.h @@ -25,7 +25,7 @@ #ifndef MM_CHUNK_TYPE_H #define MM_CHUNK_TYPE_H -#include +#include namespace MOT { diff --git a/src/gausskernel/storage/mot/core/memory/mm_def.cpp b/src/gausskernel/storage/mot/core/memory/mm_def.cpp index a5e5eac20..0b45f8d60 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_def.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_def.cpp @@ -23,7 +23,7 @@ */ #include "mm_def.h" -#include +#include namespace MOT { @@ -146,4 +146,27 @@ extern const char* MemAllocPolicyToString(MemAllocPolicy allocPolicy) } } +extern bool ValidateMemReserveMode(const char* reserveModeStr) +{ + if (MemReserveModeFromString(reserveModeStr) == MemReserveMode::MEM_RESERVE_INVALID) { + return false; + } + return true; +} + +extern bool ValidateMemStorePolicy(const char* storePolicyStr) +{ + if (MemStorePolicyFromString(storePolicyStr) == MemStorePolicy::MEM_STORE_INVALID) { + return false; + } + return true; +} + +extern bool ValidateMemAllocPolicy(const char* allocPolicyStr) +{ + if (MemAllocPolicyFromString(allocPolicyStr) == MemAllocPolicy::MEM_ALLOC_POLICY_INVALID) { + return false; + } + return true; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/mm_def.h b/src/gausskernel/storage/mot/core/memory/mm_def.h index 7cd249e04..b1485a94d 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_def.h +++ b/src/gausskernel/storage/mot/core/memory/mm_def.h @@ -25,14 +25,14 @@ #ifndef MM_DEF_H #define MM_DEF_H -#include -#include +#include +#include #include #include "type_formatter.h" /** @define General alignment macro. */ -#define MEM_ALIGN(size, align) (((size) + (align)-1) / (align) * (align)) +#define MEM_ALIGN(size, align) ((((size) + (align) - 1) / (align)) * (align)) /** @define L1 Cache line size in bytes. */ #define L1_CACHE_LINE 64 @@ -53,13 +53,13 @@ * @define Utility macro for defining L1 cache line aligned structure size (rounds up to next cache * line alignment). */ -#define L1_ALIGNED_SIZEOF(T) ((sizeof(T) + L1_CACHE_LINE - 1) / L1_CACHE_LINE * L1_CACHE_LINE) +#define L1_ALIGNED_SIZEOF(T) (((sizeof(T) + L1_CACHE_LINE - 1) / L1_CACHE_LINE) * L1_CACHE_LINE) /** * @define Utility macro for defining L1 cache line aligned structure start address (rounds up to * next cache line alignment). */ -#define L1_ALIGNED_PTR(P) ((((uintptr_t)(P)) + L1_CACHE_LINE - 1) / L1_CACHE_LINE * L1_CACHE_LINE) +#define L1_ALIGNED_PTR(P) (((((uintptr_t)(P)) + L1_CACHE_LINE - 1) / L1_CACHE_LINE) * L1_CACHE_LINE) /** @define Half L1 Cache line size in bytes. */ #define HALF_L1_CACHE_LINE (L1_CACHE_LINE / 2) @@ -68,7 +68,7 @@ * @define Utility macro for defining L1 cache line aligned structure size (rounds up to next half * cache line alignment). */ -#define HALF_L1_ALIGNED_SIZEOF(T) ((sizeof(T) + HALF_L1_CACHE_LINE - 1) / HALF_L1_CACHE_LINE * HALF_L1_CACHE_LINE) +#define HALF_L1_ALIGNED_SIZEOF(T) (((sizeof(T) + HALF_L1_CACHE_LINE - 1) / HALF_L1_CACHE_LINE) * HALF_L1_CACHE_LINE) /** @define Constant for one kilobyte. */ #define KILO_BYTE 1024 @@ -98,7 +98,7 @@ #define PAGE_SIZE_BYTES (PAGE_SIZE_KB * KILO_BYTE) /** @define Utility macro for calculating page aligned sizes (rounds up to next page size). */ -#define PAGE_ALIGNED_SIZE(size) (((size) + PAGE_SIZE_BYTES - 1) / PAGE_SIZE_BYTES * PAGE_SIZE_BYTES) +#define PAGE_ALIGNED_SIZE(size) ((((size) + PAGE_SIZE_BYTES - 1) / PAGE_SIZE_BYTES) * PAGE_SIZE_BYTES) // System Limits @@ -188,6 +188,13 @@ extern MemReserveMode MemReserveModeFromString(const char* reserveModeStr); */ extern const char* MemReserveModeToString(MemReserveMode reserveMode); +/** + * @brief Validates the memory reserve mode string value. + * @param reserveModeStr The reserve mode string. + * @return True if memory reserve mode is valid, otherwise false. + */ +extern bool ValidateMemReserveMode(const char* reserveModeStr); + /** * @class TypeFormatter * @brief Specialization of TypeFormatter with [ T = MemReserveMode ]. @@ -245,6 +252,13 @@ extern MemStorePolicy MemStorePolicyFromString(const char* storePolicyStr); */ extern const char* MemStorePolicyToString(MemStorePolicy storePolicy); +/** + * @brief Validates the memory storage policy string value. + * @param storePolicyStr The storage policy string. + * @return True if storage policy is valid, otherwise false. + */ +extern bool ValidateMemStorePolicy(const char* storePolicyStr); + /** * @class TypeFormatter * @brief Specialization of TypeFormatter with [ T = MemStorePolicy ]. @@ -337,6 +351,13 @@ extern MemAllocPolicy MemAllocPolicyFromString(const char* allocPolicyStr); */ extern const char* MemAllocPolicyToString(MemAllocPolicy allocPolicy); +/** + * @brief Validates the allocation policy string value. + * @param allocPolicyStr The memory allocation policy string. + * @return True if allocation policy is valid, otherwise false. + */ +extern bool ValidateMemAllocPolicy(const char* allocPolicyStr); + /** * @class TypeFormatter * @brief Specialization of TypeFormatter with [ T = MemAllocPolicy ]. diff --git a/src/gausskernel/storage/mot/core/memory/mm_global_api.cpp b/src/gausskernel/storage/mot/core/memory/mm_global_api.cpp index e2e484b10..7d47aa203 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_global_api.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_global_api.cpp @@ -23,9 +23,9 @@ * ------------------------------------------------------------------------- */ -#include +#include #include -#include +#include #include "mm_global_api.h" #include "mm_cfg.h" @@ -66,7 +66,6 @@ extern int MemGlobalApiInit() { MOT_LOG_TRACE("Initializing Global Memory API"); int result = GlobalAllocatorsInit(); - if (result == 0) { MOT_LOG_TRACE("Global Memory API initialized successfully"); } else { @@ -86,7 +85,7 @@ extern void MemGlobalApiDestroy() inline void* MemGlobalAllocInline(uint64_t objectSizeBytes, uint32_t alignment, int node) { - void* object = NULL; + void* object = nullptr; if (objectSizeBytes <= MEM_SESSION_MAX_ALLOC_SIZE) { MemLockAcquire(&globalAllocators[node]->m_lock); if (alignment > 0) { @@ -95,17 +94,21 @@ inline void* MemGlobalAllocInline(uint64_t objectSizeBytes, uint32_t alignment, object = MemSessionAllocatorAlloc(&globalAllocators[node]->m_allocator, objectSizeBytes); } MemLockRelease(&globalAllocators[node]->m_lock); - } else { + } else if ((alignment == 0) || ((PAGE_SIZE_BYTES % alignment) == 0)) { + // page size is a multiple of required alignment + // allocate as a large/huge allocation, since we have native page alignment MOT_LOG_DEBUG("Trying to allocate %" PRIu64 " global memory bytes as huge memory", objectSizeBytes); object = MemHugeAlloc(objectSizeBytes, node, MEM_ALLOC_GLOBAL, &(globalAllocators[node]->m_allocator.m_hugeChunkList), &globalAllocators[node]->m_lock); - } - - if (object != NULL) { - // MemoryStatisticsProvider::getInstance().addNodeObjectsAllocated(node, objectSizeBytes); + } else { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_MEMORY_SIZE, + "Global Memory Allocation", + "Unsupported alignment %u and size %" PRIu64 " for aligned allocations", + alignment, + objectSizeBytes); } return object; @@ -133,7 +136,7 @@ extern void* MemGlobalAllocAlignedOnNode(uint64_t objectSizeBytes, uint32_t alig static void* MemGlobalReallocSmall(void* object, uint64_t newSizeBytes, MemReallocFlags flags, int node) { - void* newObject = NULL; + void* newObject = nullptr; errno_t erc; if (newSizeBytes <= MEM_SESSION_MAX_ALLOC_SIZE) { @@ -142,7 +145,7 @@ static void* MemGlobalReallocSmall(void* object, uint64_t newSizeBytes, MemReall newObject = MemSessionAllocatorRealloc(&globalAllocators[node]->m_allocator, object, newSizeBytes, flags); MemLockRelease(&globalAllocators[node]->m_lock); } else { - MOT_LOG_TRACE("Reallocating %" PRIu64 " global memory bytes from global allocator %d into huge buffer", + MOT_LOG_DEBUG("Reallocating %" PRIu64 " global memory bytes from global allocator %d into huge buffer", newSizeBytes, node); newObject = MemGlobalAlloc(newSizeBytes); @@ -179,7 +182,7 @@ static void* MemGlobalReallocSmall(void* object, uint64_t newSizeBytes, MemReall static void* MemGlobalReallocHuge( void* object, uint64_t newSizeBytes, MemReallocFlags flags, MemRawChunkHeader* rawChunk, int node) { - void* newObject = NULL; + void* newObject = nullptr; errno_t erc; if (newSizeBytes > MEM_SESSION_MAX_ALLOC_SIZE) { @@ -227,21 +230,31 @@ static void* MemGlobalReallocHuge( extern void* MemGlobalRealloc(void* object, uint64_t newSizeBytes, MemReallocFlags flags) { - void* result = NULL; - - // we search the object's chunk so we can tell to which node it belongs - MemRawChunkHeader* chunk = (MemRawChunkHeader*)MemRawChunkDirLookup(object); - if (unlikely(chunk == NULL)) { - MOT_LOG_ERROR("Attempt to free corrupt global object %p: source chunk not found", object); + void* result = nullptr; + if (object == nullptr) { + result = MemGlobalAlloc(newSizeBytes); + if (result != nullptr) { + if (flags == MEM_REALLOC_ZERO) { + errno_t erc = memset_s(result, newSizeBytes, 0, newSizeBytes); + securec_check(erc, "\0", "\0"); + } + } } else { - if (likely(chunk->m_chunkType == MEM_CHUNK_TYPE_SMALL_OBJECT)) { - MemGlobalReallocSmall(object, newSizeBytes, flags, chunk->m_node); - } else if (chunk->m_chunkType == MEM_CHUNK_TYPE_HUGE_OBJECT) { - MemGlobalReallocHuge(object, newSizeBytes, flags, chunk, chunk->m_node); + // we search the object's chunk so we can tell to which node it belongs + MemRawChunkHeader* chunk = (MemRawChunkHeader*)MemRawChunkDirLookup(object); + if (unlikely(chunk == nullptr)) { + MOT_LOG_ERROR("Attempt to free corrupt global object %p: source chunk not found", object); } else { - MOT_LOG_ERROR("Attempt to free invalid global object %p: source chunk type %s is not a small object chunk", - object, - MemChunkTypeToString(chunk->m_chunkType)); + if (likely(chunk->m_chunkType == MEM_CHUNK_TYPE_SMALL_OBJECT)) { + result = MemGlobalReallocSmall(object, newSizeBytes, flags, chunk->m_node); + } else if (chunk->m_chunkType == MEM_CHUNK_TYPE_HUGE_OBJECT) { + result = MemGlobalReallocHuge(object, newSizeBytes, flags, chunk, chunk->m_node); + } else { + MOT_LOG_ERROR( + "Attempt to free invalid global object %p: source chunk type %s is not a small object chunk", + object, + MemChunkTypeToString(chunk->m_chunkType)); + } } } return result; @@ -251,7 +264,7 @@ extern void MemGlobalFree(void* object) { // we search the object's chunk so we can tell to which node it belongs MemRawChunkHeader* chunk = (MemRawChunkHeader*)MemRawChunkDirLookup(object); - if (unlikely(chunk == NULL)) { + if (unlikely(chunk == nullptr)) { MOT_LOG_ERROR("Attempt to free corrupt global object %p: source chunk not found", object); } else { int node = chunk->m_node; @@ -341,11 +354,12 @@ extern uint32_t MemGlobalGetAllStats(MemGlobalAllocatorStats* globalStatsArray, MemGlobalAllocator* objectAllocator = globalAllocators[node]; if (objectAllocator) { MemGlobalAllocatorStats* stats = &globalStatsArray[entriesReported]; - MemLockAcquire(&objectAllocator->m_lock); - MemSessionAllocatorGetStats(&objectAllocator->m_allocator, stats); - MemLockRelease(&objectAllocator->m_lock); - if (++entriesReported == nodeCount) { - break; + if (MemLockTryAcquire(&objectAllocator->m_lock) == MOT_NO_ERROR) { + MemSessionAllocatorGetStats(&objectAllocator->m_allocator, stats); + MemLockRelease(&objectAllocator->m_lock); + if (++entriesReported == nodeCount) { + break; + } } } } @@ -400,12 +414,13 @@ extern void MemGlobalPrintSummary(const char* name, LogLevel logLevel, bool full aggStats.m_realUsedSize / MEGA_BYTE); if (fullReport) { - double memUtilInternal = ((double)aggStats.m_usedSize) / ((double)aggStats.m_realUsedSize) * 100.0; - double memUtilExternal = ((double)aggStats.m_usedSize) / aggStats.m_reservedSize * 100.0; + double memUtilInternal = (((double)aggStats.m_usedSize) / ((double)aggStats.m_realUsedSize)) * 100.0; + double memUtilExternal = (((double)aggStats.m_usedSize) / aggStats.m_reservedSize) * 100.0; uint32_t minRequiredChunks = (aggStats.m_usedSize + MEM_CHUNK_SIZE_MB * MEGA_BYTE - 1) / (MEM_CHUNK_SIZE_MB * MEGA_BYTE); - double maxUtilExternal = - ((double)aggStats.m_usedSize) / (minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE) * 100.0; + double maxUtilExternal = (((double)aggStats.m_usedSize) / + static_cast(minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE)) * + 100.0; MOT_LOG(logLevel, "Global Memory API %s [Peak]: Reserved = %" PRIu64 " MB, Requested = %" PRIu64 @@ -488,7 +503,7 @@ static void GlobalAllocatorsDestroy() if (globalAllocators != nullptr) { for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { if (globalAllocators[i] != nullptr) { - MemLockDestroy(&globalAllocators[i]->m_lock); + (void)MemLockDestroy(&globalAllocators[i]->m_lock); MemSessionAllocatorDestroy(&globalAllocators[i]->m_allocator); MemNumaFreeLocal(globalAllocators[i], sizeof(MemGlobalAllocator), i); globalAllocators[i] = nullptr; @@ -540,15 +555,15 @@ extern "C" void MemGlobalApiDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemGlobalApiToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } extern "C" int MemGlobalApiAnalyze(void* buffer) { for (uint32_t i = 0; i < MOT::g_memGlobalCfg.m_nodeCount; ++i) { - fprintf(stderr, "Searching buffer %p in global allocator %u...\n", buffer, i); + (void)fprintf(stderr, "Searching buffer %p in global allocator %u...\n", buffer, i); if (MOT::globalAllocators[i]) { if (MemSessionAllocatorAnalyze(MOT::globalAllocators[i], buffer)) { return 1; diff --git a/src/gausskernel/storage/mot/core/memory/mm_global_api.h b/src/gausskernel/storage/mot/core/memory/mm_global_api.h index 9c2839842..d8b32875d 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_global_api.h +++ b/src/gausskernel/storage/mot/core/memory/mm_global_api.h @@ -30,7 +30,7 @@ #include "utilities.h" #include "mm_session_allocator.h" -#include +#include // unlike session-local memory API, which provides session-local objects that can be used only in session context, // global memory API provides objects that can be allocated by one thread, and be used and deallocated by other threads. diff --git a/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.cpp b/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.cpp index e60167b9d..6d0e4f12d 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.cpp @@ -22,7 +22,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "global.h" #include "mm_huge_object_allocator.h" @@ -36,9 +36,9 @@ namespace MOT { DECLARE_LOGGER(HugeAlloc, Memory) // Memory Usage Counters -static uint64_t memUsedBytes[MEM_MAX_NUMA_NODES]; -static uint64_t memRequestedBytes[MEM_MAX_NUMA_NODES]; -static uint64_t objectCount[MEM_MAX_NUMA_NODES]; +static uint64_t g_memUsedBytes[MEM_MAX_NUMA_NODES]; +static uint64_t g_memRequestedBytes[MEM_MAX_NUMA_NODES]; +static uint64_t g_objectCount[MEM_MAX_NUMA_NODES]; static void MemSessionRecordHugeChunk( MemVirtualHugeChunkHeader* chunkHeader, MemVirtualHugeChunkHeader** chunkHeaderList); static void MemSessioUnrecordHugeChunk( @@ -46,11 +46,11 @@ static void MemSessioUnrecordHugeChunk( extern void MemHugeInit() { - errno_t erc = memset_s(memUsedBytes, sizeof(memUsedBytes), 0, sizeof(memUsedBytes)); + errno_t erc = memset_s(g_memUsedBytes, sizeof(g_memUsedBytes), 0, sizeof(g_memUsedBytes)); securec_check(erc, "\0", "\0"); - erc = memset_s(memRequestedBytes, sizeof(memRequestedBytes), 0, sizeof(memRequestedBytes)); + erc = memset_s(g_memRequestedBytes, sizeof(g_memRequestedBytes), 0, sizeof(g_memRequestedBytes)); securec_check(erc, "\0", "\0"); - erc = memset_s(objectCount, sizeof(objectCount), 0, sizeof(objectCount)); + erc = memset_s(g_objectCount, sizeof(g_objectCount), 0, sizeof(g_objectCount)); securec_check(erc, "\0", "\0"); } @@ -82,9 +82,9 @@ extern void* MemHugeAlloc(uint64_t size, int node, MemAllocType allocType, MemVi chunkHeader->m_chunk = chunk; MemRawChunkDirInsertEx(chunk, alignedSize / (MEM_CHUNK_SIZE_MB * MEGA_BYTE), chunkHeader); - MOT_ATOMIC_ADD(memUsedBytes[node], alignedSize); - MOT_ATOMIC_ADD(memRequestedBytes[node], size); - MOT_ATOMIC_INC(objectCount[node]); + MOT_ATOMIC_ADD(g_memUsedBytes[node], alignedSize); + MOT_ATOMIC_ADD(g_memRequestedBytes[node], size); + MOT_ATOMIC_INC(g_objectCount[node]); if (lock != nullptr) { MemLockAcquire(lock); @@ -120,9 +120,9 @@ extern void MemHugeFree(MemVirtualHugeChunkHeader* chunkHeader, void* object, MemLockRelease(lock); } - MOT_ATOMIC_SUB(memUsedBytes[chunkHeader->m_node], chunkHeader->m_chunkSizeBytes); - MOT_ATOMIC_SUB(memRequestedBytes[chunkHeader->m_node], chunkHeader->m_objectSizeBytes); - MOT_ATOMIC_DEC(objectCount[chunkHeader->m_node]); + MOT_ATOMIC_SUB(g_memUsedBytes[chunkHeader->m_node], chunkHeader->m_chunkSizeBytes); + MOT_ATOMIC_SUB(g_memRequestedBytes[chunkHeader->m_node], chunkHeader->m_objectSizeBytes); + MOT_ATOMIC_DEC(g_objectCount[chunkHeader->m_node]); // clear the chunk directory MemRawChunkDirRemoveEx(object, chunkHeader->m_chunkSizeBytes / (MEM_CHUNK_SIZE_MB * MEGA_BYTE)); @@ -143,16 +143,41 @@ extern void* MemHugeRealloc(MemVirtualHugeChunkHeader* chunkHeader, void* object errno_t erc; // locate the proper list to see if the size still fits void* newObject = NULL; + uint64_t objectSizeBytes = chunkHeader->m_objectSizeBytes; if (chunkHeader->m_chunkSizeBytes >= newSizeBytes) { // new object fits newObject = object; // size fits, we are done - MOT_ATOMIC_SUB(memRequestedBytes[chunkHeader->m_node], chunkHeader->m_objectSizeBytes); - MOT_ATOMIC_ADD(memRequestedBytes[chunkHeader->m_node], newSizeBytes); + MOT_ATOMIC_SUB(g_memRequestedBytes[chunkHeader->m_node], chunkHeader->m_objectSizeBytes); + MOT_ATOMIC_ADD(g_memRequestedBytes[chunkHeader->m_node], newSizeBytes); chunkHeader->m_objectSizeBytes = newSizeBytes; + + // attention: when object is reallocated in-place we treat flags a bit differently, but achieve the + // same effect, so MEM_REALLOC_COPY has no effect + if (flags == MEM_REALLOC_ZERO) { + erc = memset_s(newObject, newSizeBytes, 0, newSizeBytes); + securec_check(erc, "\0", "\0"); + } else if (flags == MEM_REALLOC_COPY_ZERO) { + // attention: new size may be smaller + if (newSizeBytes > objectSizeBytes) { + erc = memset_s(((char*)newObject) + objectSizeBytes, + newSizeBytes - objectSizeBytes, + 0, + newSizeBytes - objectSizeBytes); + securec_check(erc, "\0", "\0"); + } else { +#ifdef MOT_DEBUG + // set dead land pattern in the reduced tail of the object + erc = memset_s(((char*)newObject) + newSizeBytes, + chunkHeader->m_chunkSizeBytes - newSizeBytes, + MEM_DEAD_LAND, + objectSizeBytes - newSizeBytes); + securec_check(erc, "\0", "\0"); +#endif + } + } } else { // allocate new object, copy/zero data if required and free old object newObject = MemHugeAlloc(newSizeBytes, chunkHeader->m_node, chunkHeader->m_allocType, chunkHeaderList, lock); if (newObject != NULL) { - uint64_t objectSizeBytes = chunkHeader->m_objectSizeBytes; if (flags == MEM_REALLOC_COPY) { // attention: new object size may be smaller erc = memcpy_s(newObject, newSizeBytes, object, std::min(newSizeBytes, objectSizeBytes)); @@ -212,9 +237,9 @@ extern void MemHugeGetStats(MemHugeAllocStats* stats) errno_t erc = memset_s(stats, sizeof(MemHugeAllocStats), 0, sizeof(MemHugeAllocStats)); securec_check(erc, "\0", "\0"); for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { - stats->m_memUsedBytes[i] = MOT_ATOMIC_LOAD(memUsedBytes[i]); - stats->m_memRequestedBytes[i] = MOT_ATOMIC_LOAD(memRequestedBytes[i]); - stats->m_objectCount[i] = MOT_ATOMIC_LOAD(objectCount[i]); + stats->m_memUsedBytes[i] = MOT_ATOMIC_LOAD(g_memUsedBytes[i]); + stats->m_memRequestedBytes[i] = MOT_ATOMIC_LOAD(g_memRequestedBytes[i]); + stats->m_objectCount[i] = MOT_ATOMIC_LOAD(g_objectCount[i]); } } diff --git a/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.h b/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.h index fe0f0f3d5..40186b6aa 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.h +++ b/src/gausskernel/storage/mot/core/memory/mm_huge_object_allocator.h @@ -31,7 +31,7 @@ #include "string_buffer.h" #include "utilities.h" -#include +#include namespace MOT { diff --git a/src/gausskernel/storage/mot/core/memory/mm_lock.h b/src/gausskernel/storage/mot/core/memory/mm_lock.h index fef849d30..29ee73d42 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_lock.h +++ b/src/gausskernel/storage/mot/core/memory/mm_lock.h @@ -29,7 +29,7 @@ #include "mot_error.h" #include -#include +#include namespace MOT { /** @struct MemLock L1-cache line isolated lock. */ @@ -99,7 +99,7 @@ inline int MemLockDestroy(MemLock* lock) */ inline void MemLockAcquire(MemLock* lock) { - pthread_spin_lock(&lock->m_lock); + (void)pthread_spin_lock(&lock->m_lock); } /** @@ -109,7 +109,7 @@ inline void MemLockAcquire(MemLock* lock) */ inline int MemLockTryAcquire(MemLock* lock) { - int result = 0; + int result = MOT_NO_ERROR; int rc = pthread_spin_trylock(&lock->m_lock); if (rc != 0) { if (rc == EBUSY) { @@ -128,7 +128,7 @@ inline int MemLockTryAcquire(MemLock* lock) */ inline void MemLockRelease(MemLock* lock) { - pthread_spin_unlock(&lock->m_lock); + (void)pthread_spin_unlock(&lock->m_lock); } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/mm_numa.cpp b/src/gausskernel/storage/mot/core/memory/mm_numa.cpp index 7635b173c..8b23ac77e 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_numa.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_numa.cpp @@ -23,7 +23,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "mm_numa.h" #include "mm_cfg.h" @@ -66,8 +66,8 @@ static void UpdateLocalStats(uint64_t size, int node) peak = peakLocalMemoryBytes[node]; // retry } } - MemoryStatisticsProvider::m_provider->AddNumaLocalAllocated(size); - DetailedMemoryStatisticsProvider::m_provider->AddNumaLocalAllocated(node, size); + MemoryStatisticsProvider::GetInstance().AddNumaLocalAllocated(size); + DetailedMemoryStatisticsProvider::GetInstance().AddNumaLocalAllocated(node, size); } static void UpdateGlobalStats(uint64_t size) @@ -82,7 +82,7 @@ static void UpdateGlobalStats(uint64_t size) peak = peakGlobalMemoryBytes; // retry } } - MemoryStatisticsProvider::m_provider->AddNumaInterleavedAllocated(size); + MemoryStatisticsProvider::GetInstance().AddNumaInterleavedAllocated(size); } extern void MemNumaInit() @@ -238,8 +238,8 @@ extern void MemNumaFreeLocal(void* buf, uint64_t size, int node) } uint64_t memUsed = MOT_ATOMIC_SUB(localMemUsedBytes[node], size); MOT_LOG_DIAG1("Decreased local node %d memory usage to %" PRIu64 " bytes", node, memUsed); - MemoryStatisticsProvider::m_provider->AddNumaLocalAllocated(-((int64_t)size)); - DetailedMemoryStatisticsProvider::m_provider->AddNumaLocalAllocated(node, -((int64_t)size)); + MemoryStatisticsProvider::GetInstance().AddNumaLocalAllocated(-((int64_t)size)); + DetailedMemoryStatisticsProvider::GetInstance().AddNumaLocalAllocated(node, -((int64_t)size)); } extern void MemNumaFreeGlobal(void* buf, uint64_t size) @@ -251,7 +251,7 @@ extern void MemNumaFreeGlobal(void* buf, uint64_t size) } uint64_t memUsed = MOT_ATOMIC_SUB(globalMemUsedBytes, size); MOT_LOG_DIAG1("Decreased global memory usage to %" PRIu64 " bytes", memUsed); - MemoryStatisticsProvider::m_provider->AddNumaInterleavedAllocated(-((int64_t)size)); + MemoryStatisticsProvider::GetInstance().AddNumaInterleavedAllocated(-((int64_t)size)); } extern void MemNumaGetStats(MemNumaStats* stats) @@ -272,27 +272,27 @@ extern void MemNumaFormatStats(int indent, const char* name, StringBuffer* strin { StringBufferAppend(stringBuffer, "%*sKernel allocation %s report:\n", indent, "", name); if (reportMode == MEM_REPORT_SUMMARY) { - uint64_t localMemUsedBytes = 0; - uint64_t localPeakMemUsedBytes = 0; + uint64_t localUsedBytes = 0; + uint64_t localPeakUsedBytes = 0; for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { - localMemUsedBytes += stats->m_localMemUsedBytes[i]; - localPeakMemUsedBytes += stats->m_peakLocalMemoryBytes[i]; + localUsedBytes += stats->m_localMemUsedBytes[i]; + localPeakUsedBytes += stats->m_peakLocalMemoryBytes[i]; } StringBufferAppend(stringBuffer, - "%*sGlobal memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", + "%*sNUMA Interleaved memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", indent + PRINT_REPORT_INDENT, "", stats->m_globalMemUsedBytes / MEGA_BYTE, stats->m_peakGlobalMemoryBytes / MEGA_BYTE); StringBufferAppend(stringBuffer, - "%*sLocal memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", + "%*sNUMA Local memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", indent + PRINT_REPORT_INDENT, "", - localMemUsedBytes / MEGA_BYTE, - localPeakMemUsedBytes / MEGA_BYTE); + localUsedBytes / MEGA_BYTE, + localPeakUsedBytes / MEGA_BYTE); } else { StringBufferAppend(stringBuffer, - "%*sGlobal memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", + "%*sNUMA Interleaved memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", indent + PRINT_REPORT_INDENT, "", stats->m_globalMemUsedBytes / MEGA_BYTE, @@ -301,7 +301,7 @@ extern void MemNumaFormatStats(int indent, const char* name, StringBuffer* strin for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { if (stats->m_peakLocalMemoryBytes[i] > 0) { StringBufferAppend(stringBuffer, - "%*sLocal memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", + "%*sNUMA Local memory usage: Current = %" PRIu64 " MB, Peak = %" PRIu64 " MB\n", indent + PRINT_REPORT_INDENT, "", stats->m_localMemUsedBytes[i] / MEGA_BYTE, @@ -355,6 +355,6 @@ extern "C" void MemNumaDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemNumaToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_numa.h b/src/gausskernel/storage/mot/core/memory/mm_numa.h index 4e465c951..e0db5bba0 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_numa.h +++ b/src/gausskernel/storage/mot/core/memory/mm_numa.h @@ -30,8 +30,8 @@ #include "string_buffer.h" #include "utilities.h" -#include -#include +#include +#include namespace MOT { diff --git a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_dir.cpp b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_dir.cpp index deb8d4020..adbb08843 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_dir.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_dir.cpp @@ -22,7 +22,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "mm_raw_chunk_dir.h" #include "mm_def.h" @@ -489,7 +489,7 @@ extern "C" void MemRawChunkDirDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemRawChunkDirToString(0, "Debug Dump", stringBuffer); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.cpp b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.cpp index 7bdd40296..157141b58 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.cpp @@ -35,7 +35,7 @@ #include "mm_cfg.h" #include "mot_engine.h" -#include +#include namespace MOT { @@ -158,6 +158,20 @@ extern int MemRawChunkPoolInit(MemRawChunkPool* chunkPool, const char* poolName, } } + // allocate session reservation buffer + size_t allocSize = sizeof(MemRawChunkPool::SessionReserve) * GetMaxConnectionCount(); + chunkPool->m_sessionReservation = (MemRawChunkPool::SessionReserve*)malloc(allocSize); + if (chunkPool->m_sessionReservation == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Chunk Pool Initialization", + "Failed to allocate %u bytes for session reservation", + (unsigned)allocSize); + result = MOT_ERROR_OOM; + } else { + erc = memset_s(chunkPool->m_sessionReservation, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } + if (result != 0) { // safe cleanup MemRawChunkPoolDestroy(chunkPool); @@ -182,6 +196,25 @@ extern void MemRawChunkPoolDestroy(MemRawChunkPool* chunkPool) { MOT_LOG_TRACE("Destroying chunk pool %s on node %d", chunkPool->m_poolName, chunkPool->m_node); + // free all chunks in all reservations + if (chunkPool->m_sessionReservation != nullptr) { + uint32_t maxConns = GetMaxConnectionCount(); + for (uint32_t i = 0; i < maxConns; ++i) { + MemRawChunkPool::SessionReserve& sessionReserve = chunkPool->m_sessionReservation[i]; + if (sessionReserve.m_inReserveMode) { + sessionReserve.m_inReserveMode = 0; + MemRawChunkHeader* itr = sessionReserve.m_reservedChunks; + while (itr != nullptr) { + MemRawChunkHeader* next = itr->m_nextChunk; + MemRawChunkPoolFree(chunkPool, itr); + itr = next; + } + } + } + free(chunkPool->m_sessionReservation); + chunkPool->m_sessionReservation = nullptr; + } + // free all chunks if (chunkPool->m_nodeBuffer != NULL) { MOT_LOG_TRACE( @@ -228,6 +261,20 @@ static uint32_t MemRawChunkPoolSecureChunks(MemRawChunkPool* chunkPool, uint32_t extern MemRawChunkHeader* MemRawChunkPoolAlloc(MemRawChunkPool* chunkPool) { + // check first session reservation + ConnectionId connId = MOT_GET_CURRENT_CONNECTION_ID(); + if (connId != INVALID_CONNECTION_ID) { + MemRawChunkPool::SessionReserve* sessionReserve = &chunkPool->m_sessionReservation[connId]; + if (sessionReserve->m_inReserveMode) { + MemRawChunkHeader* chunkHeader = sessionReserve->m_reservedChunks; + if (chunkHeader != nullptr) { + sessionReserve->m_reservedChunks = chunkHeader->m_nextChunk; + --sessionReserve->m_chunkCount; + return chunkHeader; + } + } + } + // allocate one header MemRawChunkHeader* chunkHeader = (MemRawChunkHeader*)MemLFStackPop(&chunkPool->m_chunkAllocStack); if (chunkHeader == NULL) { @@ -261,7 +308,7 @@ extern MemRawChunkHeader* MemRawChunkPoolAlloc(MemRawChunkPool* chunkPool) // the actual amount pushed can theoretically be less than allocated, so update the total count if (chunksPushed != chunksAllocated - 1) { MOT_ASSERT(chunksPushed < chunksAllocated - 1); - MOT_ATOMIC_SUB(chunkPool->m_totalChunkCount, chunksAllocated - chunksPushed - 1); + MOT_ATOMIC_SUB(chunkPool->m_totalChunkCount, (chunksAllocated - chunksPushed) - 1); MOT_LOG_DEBUG( "Allocated %u chunks, but were able to push only %u, total count updated accordingly", chunksAllocated, @@ -281,12 +328,12 @@ extern MemRawChunkHeader* MemRawChunkPoolAlloc(MemRawChunkPool* chunkPool) } else if (chunkPool->m_subChunkPool == NULL) { // report statistics only for un-cascaded chunk pools if (chunkPool->m_allocType == MEM_ALLOC_GLOBAL) { - MemoryStatisticsProvider::m_provider->AddGlobalChunksUsed(MEM_CHUNK_SIZE_MB * MEGA_BYTE); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalChunksUsed( + MemoryStatisticsProvider::GetInstance().AddGlobalChunksUsed(MEM_CHUNK_SIZE_MB * MEGA_BYTE); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalChunksUsed( chunkPool->m_node, MEM_CHUNK_SIZE_MB * MEGA_BYTE); } else { - MemoryStatisticsProvider::m_provider->AddLocalChunksUsed(MEM_CHUNK_SIZE_MB * MEGA_BYTE); - DetailedMemoryStatisticsProvider::m_provider->AddLocalChunksUsed( + MemoryStatisticsProvider::GetInstance().AddLocalChunksUsed(MEM_CHUNK_SIZE_MB * MEGA_BYTE); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalChunksUsed( chunkPool->m_node, MEM_CHUNK_SIZE_MB * MEGA_BYTE); } } @@ -314,6 +361,18 @@ extern void MemRawChunkPoolFree(MemRawChunkPool* chunkPool, void* chunk) MemRawChunkHeader* chunkHeader = (MemRawChunkHeader*)chunk; chunkHeader->m_chunkType = MEM_CHUNK_TYPE_RAW; + // check first session reservation + ConnectionId connId = MOT_GET_CURRENT_CONNECTION_ID(); + if (connId != INVALID_CONNECTION_ID) { + MemRawChunkPool::SessionReserve* sessionReserve = &chunkPool->m_sessionReservation[connId]; + if (sessionReserve->m_inReserveMode) { + chunkHeader->m_nextChunk = sessionReserve->m_reservedChunks; + sessionReserve->m_reservedChunks = chunkHeader; + ++sessionReserve->m_chunkCount; + return; + } + } + bool shouldPush = true; if (chunkPool->m_storePolicy == MEM_STORE_COMPACT) { uint32_t totalChunks = MOT_ATOMIC_LOAD(chunkPool->m_totalChunkCount); @@ -358,17 +417,80 @@ extern void MemRawChunkPoolFree(MemRawChunkPool* chunkPool, void* chunk) if (chunkPool->m_subChunkPool == NULL) { // report statistics only for un-cascaded chunk pools if (chunkPool->m_allocType == MEM_ALLOC_GLOBAL) { - MemoryStatisticsProvider::m_provider->AddGlobalChunksUsed(-((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalChunksUsed( + MemoryStatisticsProvider::GetInstance().AddGlobalChunksUsed(-((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalChunksUsed( chunkPool->m_node, -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); } else { - MemoryStatisticsProvider::m_provider->AddLocalChunksUsed(-((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); - DetailedMemoryStatisticsProvider::m_provider->AddLocalChunksUsed( + MemoryStatisticsProvider::GetInstance().AddLocalChunksUsed(-((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalChunksUsed( chunkPool->m_node, -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); } } } +extern uint32_t MemRawChunkPoolReserveSession(MemRawChunkPool* chunkPool, uint32_t chunkCount) +{ + ConnectionId connId = MOT_GET_CURRENT_CONNECTION_ID(); + if (connId == INVALID_CONNECTION_ID) { + MOT_LOG_WARN("Ignoring attempt to reserve global memory for current session: missing connection identifier"); + return 0; + } + MemRawChunkPool::SessionReserve* sessionReserve = &chunkPool->m_sessionReservation[connId]; + + // first reset reserve mode for current session, then allocate chunks and reserve them + // NOTE: We might have leftovers from a prior reservation + sessionReserve->m_inReserveMode = 0; + while (sessionReserve->m_chunkCount < chunkCount) { + MemRawChunkHeader* chunk = MemRawChunkPoolAlloc(chunkPool); + if (chunk != nullptr) { + chunk->m_nextChunk = sessionReserve->m_reservedChunks; + sessionReserve->m_reservedChunks = chunk; + ++sessionReserve->m_chunkCount; + if (MOT_ATOMIC_LOAD(chunkPool->m_emergencyMode) != 0) { + // pool entered emergency state, so stop reserving on this pool + MOT_LOG_TRACE("Stopping reservation for current session on chunk pool %s due to emergency state", + chunkPool->m_poolName); + break; + } + } else { + break; + } + } + // NOTE: even if we were not able to reserve even one chunk, we still raise reserve mode flag. Caller is + // responsible to reset reserve mode back to zero. + sessionReserve->m_inReserveMode = 1; + + // a prior reservation may have left more than needed, so we return the minimum + return std::min(sessionReserve->m_chunkCount, chunkCount); +} + +extern uint32_t MemRawChunkPoolUnreserveSession(MemRawChunkPool* chunkPool, uint32_t chunkCount) +{ + ConnectionId connId = MOT_GET_CURRENT_CONNECTION_ID(); + if (connId == INVALID_CONNECTION_ID) { + MOT_LOG_WARN("Ignoring attempt to unreserve global memory for current session: missing connection identifier"); + return 0; + } + uint32_t count = 0; + MemRawChunkPool::SessionReserve* sessionReserve = &chunkPool->m_sessionReservation[connId]; + + sessionReserve->m_inReserveMode = 0; + while (sessionReserve->m_reservedChunks != nullptr) { + MemRawChunkHeader* chunk = sessionReserve->m_reservedChunks; + sessionReserve->m_reservedChunks = chunk->m_nextChunk; + MemRawChunkPoolFree(chunkPool, chunk); + count++; + if (chunkCount > 0 && count == chunkCount) { + break; + } + } + sessionReserve->m_chunkCount -= count; + if (sessionReserve->m_chunkCount != 0) { + sessionReserve->m_inReserveMode = 1; + } + return count; +} + extern void MemRawChunkPoolPrint(const char* name, LogLevel logLevel, MemRawChunkPool* chunkPool, MemReportMode reportMode /* = MEM_REPORT_SUMMARY */) { @@ -466,8 +588,13 @@ static void StopAsyncChunkReserve(AsyncReserveData* asyncReserveData, uint32_t t taskCount, chunkPool->m_poolName, chunkPool->m_node); + int rc = 0; for (uint32_t i = 0; i < taskCount; ++i) { - pthread_join(asyncReserveData->m_reserveThreads[i], NULL); + rc = pthread_join(asyncReserveData->m_reserveThreads[i], NULL); + if (rc) { + MOT_REPORT_SYSTEM_ERROR_CODE( + rc, pthread_join, "Async chunk reserve ending", "Failed to join memory reservation thread"); + } } MOT_LOG_WARN("All pre-allocation threads aborted"); } @@ -618,7 +745,7 @@ static int ReserveChunksWait(MemRawChunkPool* chunkPool, uint32_t reserveChunkCo if (result == 0) { uint64_t endTime = GetSysClock(); double workTimeSeconds = CpuCyclesLevelTime::CyclesToSeconds(endTime - asyncReserveData->m_startTime); - double memGb = reserveChunkCount * MEM_CHUNK_SIZE_MB / 1024.0f; + double memGb = (static_cast(reserveChunkCount) * MEM_CHUNK_SIZE_MB) / 1024.0f; MOT_LOG_TRACE("Finished reserving %u chunks (%0.2f GB) for chunk pool %s on node %d within %0.4f seconds", reserveChunkCount, memGb, @@ -695,14 +822,15 @@ static void FreeChunk(MemRawChunkPool* chunkPool, MemRawChunkHeader* chunkHeader (unsigned)g_memGlobalCfg.m_chunkAllocPolicy); return; } - MemoryStatisticsProvider::m_provider->AddGlobalChunksReserved( + MemoryStatisticsProvider::GetInstance().AddGlobalChunksReserved( -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalChunksReserved( + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalChunksReserved( chunkPool->m_node, -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); } else { MemNumaFreeLocal(chunkHeader, MEM_CHUNK_SIZE_MB * MEGA_BYTE, chunkPool->m_node); - MemoryStatisticsProvider::m_provider->AddLocalChunksReserved(-((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); - DetailedMemoryStatisticsProvider::m_provider->AddLocalChunksReserved( + MemoryStatisticsProvider::GetInstance().AddLocalChunksReserved( + -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalChunksReserved( chunkPool->m_node, -((int64_t)MEM_CHUNK_SIZE_MB * MEGA_BYTE)); } } @@ -770,7 +898,7 @@ static MemRawChunkHeader* AllocateChunkFromKernel(MemRawChunkPool* chunkPool, si return nullptr; } if (chunk) { - MemoryStatisticsProvider::m_provider->AddGlobalChunksReserved(allocSize); + MemoryStatisticsProvider::GetInstance().AddGlobalChunksReserved(allocSize); } } else { if (g_memGlobalCfg.m_chunkAllocPolicy == MEM_ALLOC_POLICY_NATIVE) { @@ -784,8 +912,8 @@ static MemRawChunkHeader* AllocateChunkFromKernel(MemRawChunkPool* chunkPool, si chunk = (MemRawChunkHeader*)MemNumaAllocAlignedLocal(allocSize, align, chunkPool->m_node); } if (chunk) { - MemoryStatisticsProvider::m_provider->AddLocalChunksReserved(allocSize); - DetailedMemoryStatisticsProvider::m_provider->AddLocalChunksReserved(chunkPool->m_node, allocSize); + MemoryStatisticsProvider::GetInstance().AddLocalChunksReserved(allocSize); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalChunksReserved(chunkPool->m_node, allocSize); } } return chunk; @@ -1042,7 +1170,7 @@ static void* ReserveWorker(void* param) knl_thread_mot_init(); if (workerId > 0) { // first worker is invoked in caller context and not spawned in a new thread - AllocThreadId(); + (void)AllocThreadId(); } // we must be affined to the correct NUMA node for native allocation policy @@ -1067,7 +1195,6 @@ static void* ReserveWorker(void* param) #else int result = AllocateChunks(chunkPool, reserveParam->m_chunkCount); #endif - if (result == 0) { MOT_LOG_TRACE("Chunk reservation worker %u for chunk pool %s on node %d finished successfully", workerId, @@ -1112,8 +1239,8 @@ extern "C" void MemRawChunkPoolDump(void* arg) MOT::StringBufferApply([chunkPool](MOT::StringBuffer* stringBuffer) { MOT::MemRawChunkPoolToString(0, "Debug Dump", chunkPool, stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stdout); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stdout); }); } @@ -1129,7 +1256,7 @@ extern "C" int MemRawChunkPoolAnalyze(void* pool, void* buffer) MOT::MemLFStackNode* itr = chunkPool->m_chunkAllocStack.m_head.m_node; while (itr != NULL) { if ((void*)itr->m_value == chunk) { - fprintf(stderr, + (void)fprintf(stderr, "Buffer %p found in chunk %p found in chunk pool %s allocation stack\n", buffer, chunk, diff --git a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.h b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.h index 48ed62578..c794577fc 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.h +++ b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_pool.h @@ -93,6 +93,20 @@ struct MemRawChunkPool { /** @var Private data for managing asynchronous memory reservation. */ void* m_asyncReserveData; + + /** @struct Per-session reservation data. */ + struct SessionReserve { + /** @var Reservation mode flag. */ + uint32_t m_inReserveMode; + + /** @var Amount of reserved chunks. */ + uint32_t m_chunkCount; + + /** @var Reserved chunk list. */ + MemRawChunkHeader* m_reservedChunks; + }; + /** @var Per-session reservation. */ + SessionReserve* m_sessionReservation; }; /** @@ -178,6 +192,21 @@ extern MemRawChunkHeader* MemRawChunkPoolAlloc(MemRawChunkPool* chunkPool); */ extern void MemRawChunkPoolFree(MemRawChunkPool* chunkPool, void* chunkHeader); +/** + * @brief Reserve global memory for current session. While in reserve-mode, released chunks are kept in the current + * session's reserve, rather than being released to global memory. + * @param chunkPool The chunk pool. + * @param chunkCount The number of chunks to reserve. + * @return The number of chunks reserved. + */ +extern uint32_t MemRawChunkPoolReserveSession(MemRawChunkPool* chunkPool, uint32_t chunkCount); + +/** + * @brief Release all global memory reserved for current session. + * @param chunkPool The chunk pool. + */ +extern uint32_t MemRawChunkPoolUnreserveSession(MemRawChunkPool* chunkPool, uint32_t chunkCount); + /** * @brief Prints a chunk pool into log. * @param name The name of the chunk pool to print. diff --git a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.cpp b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.cpp index 09eefc4ff..0df0bfbfd 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.cpp @@ -28,9 +28,10 @@ #include "mm_def.h" #include "utilities.h" #include "mot_error.h" +#include "mot_engine.h" -#include -#include +#include +#include namespace MOT { @@ -80,7 +81,8 @@ extern void* MemRawChunkStoreAllocGlobal(int node) void* chunk = MemRawChunkPoolAlloc(globalChunkPools[node]); if (chunk == NULL) { // reached high black mark in this pool, continue attempt in round robin - for (int i = (node + 1) % g_memGlobalCfg.m_nodeCount; i != node; i = (i + 1) % g_memGlobalCfg.m_nodeCount) { + for (uint32_t i = ((uint32_t)node + 1) % g_memGlobalCfg.m_nodeCount; i != (uint32_t)node; + i = (i + 1) % g_memGlobalCfg.m_nodeCount) { chunk = MemRawChunkPoolAlloc(globalChunkPools[i]); if (chunk != NULL) { break; @@ -102,7 +104,8 @@ extern void* MemRawChunkStoreAllocLocal(int node) void* chunk = MemRawChunkPoolAlloc(localChunkPools[node]); if (chunk == NULL) { // reached high black mark in this pool, continue attempt in round robin - for (int i = (node + 1) % g_memGlobalCfg.m_nodeCount; i != node; i = (i + 1) % g_memGlobalCfg.m_nodeCount) { + for (uint32_t i = ((uint32_t)node + 1) % g_memGlobalCfg.m_nodeCount; i != (uint32_t)node; + i = (i + 1) % g_memGlobalCfg.m_nodeCount) { chunk = MemRawChunkPoolAlloc(localChunkPools[i]); if (chunk != NULL) { break; @@ -129,6 +132,53 @@ extern void MemRawChunkStoreFreeLocal(void* chunk, int node) MemRawChunkPoolFree(localChunkPools[node], chunk); } +extern int MemRawChunkStoreReserveGlobal(int node, uint32_t chunkCount) +{ + int result = 0; + uint32_t chunksReserved = MemRawChunkPoolReserveSession(globalChunkPools[node], chunkCount); + if (chunksReserved < chunkCount) { + uint32_t chunksRequired = chunkCount - chunksReserved; + // reached high black mark in this pool, continue attempt in round robin + for (uint32_t i = ((uint32_t)node + 1) % g_memGlobalCfg.m_nodeCount; i != (uint32_t)node; + i = (i + 1) % g_memGlobalCfg.m_nodeCount) { + chunksReserved += MemRawChunkPoolReserveSession(globalChunkPools[i], chunksRequired); + if (chunksReserved == chunkCount) { + break; + } else { + chunksRequired = chunkCount - chunksReserved; + if (MOT::MOTEngine::GetInstance()->IsSoftMemoryLimitReached()) { + // stop reserving, we are out of memory + MOT_LOG_WARN( + "Stopping reservation for current session on chunk store due to global emergency state"); + break; + } + } + } + } + + if (chunksReserved < chunkCount) { + // release all reservation and return error + MemRawChunkStoreUnreserveGlobal(node, 0); + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "N/A", + "Failed to reserve %u chunks for current session: All global chunk pools are depleted"); + result = MOT_ERROR_OOM; + } + return result; +} + +extern void MemRawChunkStoreUnreserveGlobal(int node, uint32_t chunkCount) +{ + uint32_t count = 0; + // unreserve on al pools + for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { + count += MemRawChunkPoolUnreserveSession(globalChunkPools[i], chunkCount); + if (chunkCount > 0 && count == chunkCount) { + break; + } + } +} + extern void MemRawChunkStorePrint( const char* name, LogLevel logLevel, MemReportMode reportMode /* = MEM_REPORT_SUMMARY */) { @@ -258,11 +308,11 @@ static int InitChunkPools(MemRawChunkPool** chunkPools, MemReserveMode reserveMo ((double)chunkReserveCount) * MEM_CHUNK_SIZE_MB / KILO_BYTE, MemReserveModeToString(reserveMode)); - char name[32]; - erc = snprintf_s(name, sizeof(name), sizeof(name) - 1, "%s[%u]", nameCapital, i); + char tmpName[32]; + erc = snprintf_s(tmpName, sizeof(tmpName), sizeof(tmpName) - 1, "%s[%u]", nameCapital, i); securec_check_ss(erc, "\0", "\0"); result = MemRawChunkPoolInit(chunkPools[i], - name, + tmpName, i, allocType, chunkReserveCount, @@ -327,8 +377,8 @@ static int MemRawChunkStoreInitGlobal(MemReserveMode reserveMode, MemStorePolicy { int result = 0; MOT_LOG_TRACE("Initializing global chunk pools"); - uint64_t chunkReserveCount = g_memGlobalCfg.m_minGlobalMemoryMb / MEM_CHUNK_SIZE_MB / g_memGlobalCfg.m_nodeCount; - uint64_t highBlackMark = g_memGlobalCfg.m_maxGlobalMemoryMb / MEM_CHUNK_SIZE_MB / g_memGlobalCfg.m_nodeCount; + uint64_t chunkReserveCount = (g_memGlobalCfg.m_minGlobalMemoryMb / MEM_CHUNK_SIZE_MB) / g_memGlobalCfg.m_nodeCount; + uint64_t highBlackMark = (g_memGlobalCfg.m_maxGlobalMemoryMb / MEM_CHUNK_SIZE_MB) / g_memGlobalCfg.m_nodeCount; uint64_t highRedMark = highBlackMark * g_memGlobalCfg.m_highRedMarkPercent / 100; globalChunkPools = (MemRawChunkPool**)calloc(g_memGlobalCfg.m_nodeCount, sizeof(MemRawChunkPool*)); @@ -374,8 +424,8 @@ static int MemRawChunkStoreInitLocal(MemReserveMode reserveMode, MemStorePolicy { int result = 0; MOT_LOG_TRACE("Initializing local chunk pools"); - uint64_t chunkReserveCount = g_memGlobalCfg.m_minLocalMemoryMb / MEM_CHUNK_SIZE_MB / g_memGlobalCfg.m_nodeCount; - uint64_t highBlackMark = g_memGlobalCfg.m_maxLocalMemoryMb / MEM_CHUNK_SIZE_MB / g_memGlobalCfg.m_nodeCount; + uint64_t chunkReserveCount = (g_memGlobalCfg.m_minLocalMemoryMb / MEM_CHUNK_SIZE_MB) / g_memGlobalCfg.m_nodeCount; + uint64_t highBlackMark = (g_memGlobalCfg.m_maxLocalMemoryMb / MEM_CHUNK_SIZE_MB) / g_memGlobalCfg.m_nodeCount; uint64_t highRedMark = highBlackMark; // no emergency state for local pools localChunkPools = (MemRawChunkPool**)calloc(g_memGlobalCfg.m_nodeCount, sizeof(MemRawChunkPool*)); @@ -425,7 +475,7 @@ extern "C" void MemRawChunkStoreDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemRawChunkStoreToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); }); } diff --git a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.h b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.h index 3be23cf5e..e888765a0 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.h +++ b/src/gausskernel/storage/mot/core/memory/mm_raw_chunk_store.h @@ -87,6 +87,20 @@ extern void MemRawChunkStoreFreeGlobal(void* chunk, int node); */ extern void MemRawChunkStoreFreeLocal(void* chunk, int node); +/** + * @brief Reserve global memory for current session. While in reserve-mode, released chunks are kept in the current + * session's reserve, rather than being released to global memory. + * @param chunkCount The number of chunks to reserve. + * @return Zero on success, otherwise error code on failure. + */ +extern int MemRawChunkStoreReserveGlobal(int node, uint32_t chunkCount); + +/** + * @brief Release all global memory reserved for current session. + * @param bufferClass The buffer class for which an existing reservation is to be released. + */ +extern void MemRawChunkStoreUnreserveGlobal(int node, uint32_t chunkCount); + /** * @brief Prints status of all chunk pools in chunk store into log. * @param name The name to prepend to the log message. diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_allocator.cpp b/src/gausskernel/storage/mot/core/memory/mm_session_allocator.cpp index d93fdefd4..177c4fd45 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_allocator.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_session_allocator.cpp @@ -22,7 +22,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "mm_session_allocator.h" #include "session_context.h" @@ -201,11 +201,11 @@ extern void* MemSessionAllocatorAlloc(MemSessionAllocator* sessionAllocator, uin sessionAllocator->m_maxRealUsedSize = sessionAllocator->m_realUsedSize; } if (sessionAllocator->m_allocType == MEM_ALLOC_GLOBAL) { - MemoryStatisticsProvider::m_provider->AddGlobalBytesUsed(realSize); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalBytesRequested(size); + MemoryStatisticsProvider::GetInstance().AddGlobalBytesUsed(realSize); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalBytesRequested(size); } else { - MemoryStatisticsProvider::m_provider->AddSessionBytesUsed(realSize); - DetailedMemoryStatisticsProvider::m_provider->AddSessionBytesRequested(size); + MemoryStatisticsProvider::GetInstance().AddSessionBytesUsed(realSize); + DetailedMemoryStatisticsProvider::GetInstance().AddSessionBytesRequested(size); } } } @@ -250,7 +250,6 @@ extern void MemSessionAllocatorFree(MemSessionAllocator* sessionAllocator, void* { ASSERT_SESSION_ALLOCATOR_VALID(sessionAllocator); - uint32_t objectSize = 0; MemSessionObjectHeader* objectHeader = (MemSessionObjectHeader*)((char*)object - MEM_SESSION_OBJECT_HEADER_LEN); // fix to real header of aligned object @@ -297,12 +296,12 @@ extern void MemSessionAllocatorFree(MemSessionAllocator* sessionAllocator, void* sessionAllocator->m_usedSize -= objectHeader->m_objectSize.m_size; sessionAllocator->m_realUsedSize -= objectHeader->m_objectSize.m_realSize; if (sessionAllocator->m_allocType == MEM_ALLOC_GLOBAL) { - MemoryStatisticsProvider::m_provider->AddGlobalBytesUsed(-((int64_t)objectHeader->m_objectSize.m_size)); - DetailedMemoryStatisticsProvider::m_provider->AddGlobalBytesRequested( + MemoryStatisticsProvider::GetInstance().AddGlobalBytesUsed(-((int64_t)objectHeader->m_objectSize.m_size)); + DetailedMemoryStatisticsProvider::GetInstance().AddGlobalBytesRequested( -((int64_t)objectHeader->m_objectSize.m_realSize)); } else { - MemoryStatisticsProvider::m_provider->AddSessionBytesUsed(-((int64_t)objectHeader->m_objectSize.m_size)); - DetailedMemoryStatisticsProvider::m_provider->AddSessionBytesRequested( + MemoryStatisticsProvider::GetInstance().AddSessionBytesUsed(-((int64_t)objectHeader->m_objectSize.m_size)); + DetailedMemoryStatisticsProvider::GetInstance().AddSessionBytesRequested( -((int64_t)objectHeader->m_objectSize.m_realSize)); } @@ -347,6 +346,7 @@ extern void* MemSessionAllocatorRealloc( objectHeader = (MemSessionObjectHeader*)(((uint8_t*)object) - MEM_SESSION_OBJECT_HEADER_LEN); } + uint32_t objectSizeBytes = objectHeader->m_objectSize.m_size; if (objectHeader->m_objectSize.m_realSize >= newSize) { result = object; MOT_LOG_DEBUG("The old object is big enough for requested new size."); @@ -356,17 +356,41 @@ extern void* MemSessionAllocatorRealloc( // unsigned) int64_t sizeDiff = ((int64_t)newSize) - ((int64_t)objectHeader->m_objectSize.m_size); if (sessionAllocator->m_allocType == MEM_ALLOC_GLOBAL) { - MemoryStatisticsProvider::m_provider->AddGlobalBytesUsed(sizeDiff); + MemoryStatisticsProvider::GetInstance().AddGlobalBytesUsed(sizeDiff); } else { - MemoryStatisticsProvider::m_provider->AddSessionBytesUsed(sizeDiff); + MemoryStatisticsProvider::GetInstance().AddSessionBytesUsed(sizeDiff); } + // update allocator and object header statistics sessionAllocator->m_usedSize -= objectHeader->m_objectSize.m_size; sessionAllocator->m_usedSize += newSize; if (sessionAllocator->m_usedSize > sessionAllocator->m_maxUsedSize) { sessionAllocator->m_maxUsedSize = sessionAllocator->m_usedSize; } objectHeader->m_objectSize.m_size = newSize; + + // attention: when object is reallocated in-place we treat flags a bit differently, but achieve the same effect + // so MEM_REALLOC_COPY has no effect + if (flags == MEM_REALLOC_ZERO) { + erc = memset_s(result, newSize, 0, newSize); + securec_check(erc, "\0", "\0"); + } else if (flags == MEM_REALLOC_COPY_ZERO) { + // attention: new size may be smaller + if (newSize > objectSizeBytes) { + erc = memset_s( + ((char*)result) + objectSizeBytes, newSize - objectSizeBytes, 0, newSize - objectSizeBytes); + securec_check(erc, "\0", "\0"); + } else { +#ifdef MOT_DEBUG + // set dead land pattern in the reduced tail of the object + erc = memset_s(((char*)result) + newSize, + objectHeader->m_objectSize.m_realSize - newSize, + MEM_DEAD_LAND, + objectSizeBytes - newSize); + securec_check(erc, "\0", "\0"); +#endif + } + } } else { void* newObject = MemSessionAllocatorAlloc(sessionAllocator, newSize); if (newObject == NULL) { @@ -376,7 +400,6 @@ extern void* MemSessionAllocatorRealloc( newSize, (unsigned)sessionAllocator->m_threadId); } else { - uint32_t objectSizeBytes = objectHeader->m_objectSize.m_size; if (flags == MEM_REALLOC_COPY) { // attention: new size may be smaller erc = memcpy_s(newObject, newSize, object, min(objectSizeBytes, newSize)); @@ -403,9 +426,7 @@ extern void* MemSessionAllocatorRealloc( extern uint32_t MemSessionAllocatorGetObjectSize(void* object, uint32_t* requestedSize /* = NULL */) { - uint32_t objectSize = 0; MemSessionObjectHeader* objectHeader = (MemSessionObjectHeader*)((char*)object - MEM_SESSION_OBJECT_HEADER_LEN); - if (objectHeader->m_alignedOffset != 0) { MOT_LOG_DEBUG("Reallocating aligned allocation %p by offset %u", object, objectHeader->m_alignedOffset); object = ((uint8_t*)object) - objectHeader->m_alignedOffset; @@ -530,12 +551,13 @@ extern void MemSessionAllocatorFormatStats(int indent, const char* name, StringB stats->m_realUsedSize / MEGA_BYTE); if (reportMode == MEM_REPORT_DETAILED) { - double memUtilInternal = ((double)stats->m_usedSize) / ((double)stats->m_realUsedSize) * 100.0; - double memUtilExternal = ((double)stats->m_usedSize) / stats->m_reservedSize * 100.0; + double memUtilInternal = (((double)stats->m_usedSize) / ((double)stats->m_realUsedSize)) * 100.0; + double memUtilExternal = (((double)stats->m_usedSize) / stats->m_reservedSize) * 100.0; uint32_t minRequiredChunks = (stats->m_usedSize + MEM_CHUNK_SIZE_MB * MEGA_BYTE - 1) / (MEM_CHUNK_SIZE_MB * MEGA_BYTE); double maxUtilExternal = - ((double)stats->m_usedSize) / (minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE) * 100.0; + (((double)stats->m_usedSize) / static_cast(minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE)) * + 100.0; StringBufferAppend(stringBuffer, "%*sPeak Reserved : %" PRIu64 "\n", @@ -654,12 +676,11 @@ extern void MemSessionAllocatorToString(int indent, const char* name, MemSession } extern void MemSessionChunkInit( - MemSessionChunkHeader* chunk, MemChunkType chunkType, int nodeId, MemAllocType allocType) + MemSessionChunkHeader* chunk, MemChunkType chunkType, int allocatorNode, MemAllocType allocType) { chunk->m_chunkType = chunkType; - chunk->m_node = nodeId; chunk->m_allocType = allocType; - chunk->m_allocatorNode = chunk->m_node; + chunk->m_allocatorNode = allocatorNode; chunk->m_freePtr = (char*)chunk + MEM_SESSION_CHUNK_HEADER_LEN; chunk->m_endPtr = (char*)chunk + MEM_CHUNK_SIZE_MB * MEGA_BYTE; chunk->m_freeSize = MEM_CHUNK_SIZE_MB * MEGA_BYTE - MEM_SESSION_CHUNK_HEADER_LEN; @@ -880,8 +901,8 @@ extern "C" void MemSessionAllocatorDump(void* arg) MOT::StringBufferApply([sessionAllocator](MOT::StringBuffer* stringBuffer) { MOT::MemSessionAllocatorToString(0, "Debug Dump", sessionAllocator, stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -905,7 +926,7 @@ extern "C" int MemSessionAllocatorAnalyze(void* allocator, void* buffer) return 0; } - fprintf(stderr, + (void)fprintf(stderr, "Object %p found in session allocator for session %u thread %u\n", buffer, sessionAllocator->m_sessionId, @@ -914,7 +935,7 @@ extern "C" int MemSessionAllocatorAnalyze(void* allocator, void* buffer) // Step 2: print object details MOT::MemSessionObjectHeader* objectHeader = (MOT::MemSessionObjectHeader*)(((char*)buffer) - MEM_SESSION_OBJECT_HEADER_LEN); - fprintf(stderr, + (void)fprintf(stderr, "Object %p details: size = %u bytes, real size = %u bytes, session id = %u\n", buffer, objectHeader->m_objectSize.m_size, @@ -923,17 +944,17 @@ extern "C" int MemSessionAllocatorAnalyze(void* allocator, void* buffer) // Guard against double free of an object. if (objectHeader->m_sessionId == INVALID_SESSION_ID) { - fprintf(stderr, "Object %p is an already freed object\n", buffer); + (void)fprintf(stderr, "Object %p is an already freed object\n", buffer); } else if (objectHeader->m_sessionId != sessionAllocator->m_sessionId) { // Check if the object belongs to this chunk or not. - fprintf(stderr, + (void)fprintf(stderr, "Object %p is a live object marked as belonging to session %u, but is actually found in session allocator " "for session %u\n", buffer, objectHeader->m_sessionId, sessionAllocator->m_sessionId); } else { - fprintf(stderr, "Object %p is a live object\n", buffer); + (void)fprintf(stderr, "Object %p is a live object\n", buffer); } // Step 3: search buffer in all free lists (maybe this object was already freed) @@ -942,7 +963,7 @@ extern "C" int MemSessionAllocatorAnalyze(void* allocator, void* buffer) MOT::MemSessionObjectHeader* freeList = sessionAllocator->m_freeList[i]; while (freeList && !foundInList) { if (freeList == objectHeader) { - fprintf(stderr, "Object %p found in free list %u\n", buffer, i); + (void)fprintf(stderr, "Object %p found in free list %u\n", buffer, i); foundInList = true; } else { freeList = freeList->m_next; diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_allocator.h b/src/gausskernel/storage/mot/core/memory/mm_session_allocator.h index ec371e013..23aa17b85 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_allocator.h +++ b/src/gausskernel/storage/mot/core/memory/mm_session_allocator.h @@ -78,11 +78,11 @@ struct PACKED MemSessionChunkHeader { * @brief Initializes a session chunk header. * @param chunk The chunk header to initialize. * @param chunkType The type of the chunk to use (can be session or global small object). - * @param nodeId The NUMA node identifier with which the chunk is associated. + * @param allocatorNode The NUMA node identifier with which the chunk is associated. * @param allocType The type of the allocation to use (can be session local or global object). */ extern void MemSessionChunkInit( - MemSessionChunkHeader* chunk, MemChunkType chunkType, int nodeId, MemAllocType allocType); + MemSessionChunkHeader* chunk, MemChunkType chunkType, int allocatorNode, MemAllocType allocType); /** @struct MemSessionObjectSize Object size helper struct. */ struct PACKED MemSessionObjectSize { diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_api.cpp b/src/gausskernel/storage/mot/core/memory/mm_session_api.cpp index 3014d8f05..b1aca9e5e 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_api.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_session_api.cpp @@ -22,7 +22,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "mm_session_api.h" #include "mm_cfg.h" @@ -101,7 +101,7 @@ extern int MemSessionApiInit() } free(g_sessionAllocators); g_sessionAllocators = nullptr; - pthread_mutex_destroy(&g_sessionLock); + (void)pthread_mutex_destroy(&g_sessionLock); result = GetLastError(); break; } @@ -139,7 +139,7 @@ extern void MemSessionApiDestroy() // 3. Free g_sessionAllocators global array. free(g_sessionAllocators); g_sessionAllocators = nullptr; - pthread_mutex_destroy(&g_sessionLock); + (void)pthread_mutex_destroy(&g_sessionLock); MOT_LOG_TRACE("Session API destroyed"); } @@ -202,9 +202,8 @@ extern int MemSessionReserve(uint32_t sizeBytes) } else { MOT_LOG_TRACE("Initializing g_sessionAllocators[%u]", attrSet.m_connectionId, node); // 1. get the right start address of g_sessionAllocatorBuf. - MemSessionAllocator* sessionAllocator = - (MemSessionAllocator*)(((char*)g_sessionAllocatorBuf[node]) + - sizeof(MemSessionAllocator) * connectionId); + MemSessionAllocator* sessionAllocator = (MemSessionAllocator*)(((char*)g_sessionAllocatorBuf[node]) + + sizeof(MemSessionAllocator) * connectionId); // 2. Allocate raw chunks from the NUMA node local chunk pool according to the requested size. uint32_t chunkCount = (sizeBytes + MEM_CHUNK_SIZE_MB * MEGA_BYTE - 1) / (MEM_CHUNK_SIZE_MB * MEGA_BYTE); @@ -224,7 +223,7 @@ extern int MemSessionReserve(uint32_t sizeBytes) while (chunkList != nullptr) { MemSessionChunkHeader* temp = chunkList; chunkList = chunkList->m_next; - MemRawChunkStoreFreeLocal((void*)temp, node); + MemRawChunkStoreFreeLocal((void*)temp, temp->m_node); } break; @@ -240,9 +239,9 @@ extern int MemSessionReserve(uint32_t sizeBytes) sessionAllocator, attrSet.m_sessionId, connectionId, chunkList, chunkCount, MEM_ALLOC_LOCAL); // save the new allocator in the allocator array (minimize time lock is held) - pthread_mutex_lock(&g_sessionLock); + (void)pthread_mutex_lock(&g_sessionLock); g_sessionAllocators[connectionId] = sessionAllocator; - pthread_mutex_unlock(&g_sessionLock); + (void)pthread_mutex_unlock(&g_sessionLock); MOT_LOG_DEBUG("Session allocator initialized for thread id %u, connection id %u, session id: %u", (unsigned)attrSet.m_threadId, @@ -277,9 +276,9 @@ extern void MemSessionUnreserve() MemSessionAllocatorPrint("Pre-Terminate report", LogLevel::LL_TRACE, sessionAllocator); // minimize time lock is held (set NULL quickly before destroying later) - pthread_mutex_lock(&g_sessionLock); + (void)pthread_mutex_lock(&g_sessionLock); g_sessionAllocators[connectionId] = nullptr; - pthread_mutex_unlock(&g_sessionLock); + (void)pthread_mutex_unlock(&g_sessionLock); MemSessionAllocatorDestroy(sessionAllocator); } @@ -380,13 +379,15 @@ extern void* MemSessionAllocAligned(uint64_t sizeBytes, uint32_t alignment) sessionId, connectionId); result = MemSessionAllocatorAllocAligned(g_sessionAllocators[connectionId], sizeBytes, alignment); - } else if ((alignment % PAGE_SIZE_BYTES) == 0) { - // allocate as a regular allocation, since we have native 4 KB alignment + } else if ((alignment == 0) || ((PAGE_SIZE_BYTES % alignment) == 0)) { + // page size is a multiple of required alignment + // allocate as a large/huge allocation, since we have native page alignment result = MemSessionAlloc(sizeBytes); } else { MOT_REPORT_ERROR(MOT_ERROR_INVALID_MEMORY_SIZE, "Session Memory Allocation", - "Unsupported size %" PRIu64 " for aligned allocations", + "Unsupported alignment %u and size %" PRIu64 " for aligned allocations", + alignment, sizeBytes); } #endif @@ -546,6 +547,12 @@ extern void* MemSessionRealloc(void* object, uint64_t newSizeBytes, MemReallocFl if (object == nullptr) { newObject = MemSessionAlloc(newSizeBytes); + if (newObject != nullptr) { + if (flags == MEM_REALLOC_ZERO) { + errno_t erc = memset_s(newObject, newSizeBytes, 0, newSizeBytes); + securec_check(erc, "\0", "\0"); + } + } } else { #ifndef USING_LARGE_HUGE_SESSION_BUFFERS MOT_LOG_DEBUG("Reallocating %" PRIu64 " bytes by session %u from session allocator %u", @@ -689,7 +696,7 @@ extern int MemSessionGetStats(ConnectionId connectionId, MemSessionAllocatorStat { // get the session allocator for the session id int result = 0; - pthread_mutex_lock(&g_sessionLock); + (void)pthread_mutex_lock(&g_sessionLock); if (connectionId != INVALID_CONNECTION_ID) { MemSessionAllocator* sessionAllocator = g_sessionAllocators[connectionId]; @@ -702,7 +709,7 @@ extern int MemSessionGetStats(ConnectionId connectionId, MemSessionAllocatorStat } } - pthread_mutex_unlock(&g_sessionLock); + (void)pthread_mutex_unlock(&g_sessionLock); return result; } @@ -727,7 +734,7 @@ extern void MemSessionPrintCurrentStats(const char* name, LogLevel logLevel) extern uint32_t MemSessionGetAllStats(MemSessionAllocatorStats* sessionStatsArray, uint32_t sessionCount) { uint32_t entriesReported = 0; - pthread_mutex_lock(&g_sessionLock); + (void)pthread_mutex_lock(&g_sessionLock); for (uint32_t connectionId = 0; connectionId < g_memGlobalCfg.m_maxConnectionCount; ++connectionId) { MemSessionAllocator* sessionAllocator = g_sessionAllocators[connectionId]; @@ -739,7 +746,7 @@ extern uint32_t MemSessionGetAllStats(MemSessionAllocatorStats* sessionStatsArra } } } - pthread_mutex_unlock(&g_sessionLock); + (void)pthread_mutex_unlock(&g_sessionLock); return entriesReported; } @@ -790,12 +797,13 @@ extern void MemSessionPrintSummary(const char* name, LogLevel logLevel, bool ful aggStats.m_realUsedSize / MEGA_BYTE); if (fullReport) { - double memUtilInternal = ((double)aggStats.m_usedSize) / ((double)aggStats.m_realUsedSize) * 100.0; - double memUtilExternal = ((double)aggStats.m_usedSize) / aggStats.m_reservedSize * 100.0; + double memUtilInternal = (((double)aggStats.m_usedSize) / ((double)aggStats.m_realUsedSize)) * 100.0; + double memUtilExternal = (((double)aggStats.m_usedSize) / aggStats.m_reservedSize) * 100.0; uint32_t minRequiredChunks = (aggStats.m_usedSize + MEM_CHUNK_SIZE_MB * MEGA_BYTE - 1) / (MEM_CHUNK_SIZE_MB * MEGA_BYTE); - double maxUtilExternal = - ((double)aggStats.m_usedSize) / (minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE) * 100.0; + double maxUtilExternal = (((double)aggStats.m_usedSize) / + static_cast(minRequiredChunks * MEM_CHUNK_SIZE_MB * MEGA_BYTE)) * + 100.0; MOT_LOG(logLevel, "Session %s [Peak]: Reserved = %" PRIu64 " MB, Requested = %" PRIu64 " MB, Allocated = %" PRIu64 @@ -820,15 +828,15 @@ extern "C" void MemSessionApiDump() { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemSessionApiToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } extern "C" int MemSessionApiAnalyze(void* buffer) { for (uint32_t i = 0; i < MOT::g_memGlobalCfg.m_maxConnectionCount; ++i) { - fprintf(stderr, "Searching buffer %p in session allocator %u...\n", buffer, i); + (void)fprintf(stderr, "Searching buffer %p in session allocator %u...\n", buffer, i); if (MOT::g_sessionAllocators[i]) { if (MemSessionAllocatorAnalyze(MOT::g_sessionAllocators[i], buffer)) { return 1; diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_api.h b/src/gausskernel/storage/mot/core/memory/mm_session_api.h index e500354d9..55d8a0f26 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_api.h +++ b/src/gausskernel/storage/mot/core/memory/mm_session_api.h @@ -30,7 +30,7 @@ #include "mm_session_allocator.h" #include "connection_id.h" -#include +#include #include namespace MOT { @@ -316,8 +316,10 @@ public: */ MemSessionPtr& operator=(MemSessionPtr&& right) { - Reset(right.m_ptr); - right.m_ptr = nullptr; + if (this != &right) { + Reset(right.m_ptr); + right.m_ptr = nullptr; + } return *this; } @@ -328,9 +330,9 @@ public: template inline operator MemSessionPtr() { - T* m_ptr = this->m_ptr; + T* ptr = this->m_ptr; this->m_ptr = nullptr; - return MemSessionPtr(static_cast(m_ptr)); + return MemSessionPtr(static_cast(ptr)); } /** diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.cpp b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.cpp index 2c9661563..eb1bedf91 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.cpp @@ -64,16 +64,17 @@ extern int MemSessionLargeBufferListInit( return result; } -extern void MemSessionLargeBufferListOnDoubleFree( - MemSessionLargeBufferList* sessionBufferList, void* buffer, uint32_t bufferIndex) +extern void MemSessionLargeBufferListOnInvalidFree( + MemSessionLargeBufferList* sessionBufferList, void* buffer, uint64_t bufferIndex, const char* invalidType) { - MOT_LOG_PANIC("Double free of session large buffer %p [@%u] in buffer list %p (node: %d, buffer-size=%u KB, " - "allocated=%u/%u)", + MOT_LOG_PANIC("%s free of session large buffer %p [@%" PRIu64 + "] in buffer list %p (node: %d, buffer-size=%" PRIu64 " MB, allocated=%u/%u)", + invalidType, buffer, bufferIndex, sessionBufferList, MOTCurrentNumaNodeId, - sessionBufferList->m_bufferSize, + sessionBufferList->m_bufferSize / MEGA_BYTE, sessionBufferList->m_allocatedCount, sessionBufferList->m_maxBufferCount); @@ -101,8 +102,8 @@ extern void MemSessionLargeBufferListToString(int indent, const char* name, if (stats.m_allocatedBytes > 0) { if (reportMode == MEM_REPORT_SUMMARY) { StringBufferAppend(stringBuffer, - "%*sSession Large Buffer List %s [buffer-size=%u MB, max-buffers=%u]: %u MB allocated, %u MB " - "requested\n", + "%*sSession Large Buffer List %s [buffer-size=%" PRIu64 " MB, max-buffers=%u]: %" PRIu64 " MB allocated" + ", %" PRIu64 " MB requested\n", indent, "", name, @@ -112,11 +113,11 @@ extern void MemSessionLargeBufferListToString(int indent, const char* name, stats.m_requestedBytes / MEGA_BYTE); } else { StringBufferAppend(stringBuffer, - "%*sSession Large Buffer List %s: [buffer-size=%u MB, max-buffers=%u]\n", + "%*sSession Large Buffer List %s: [buffer-size=%" PRIu64 " MB, max-buffers=%u]\n", indent, "", name, - sessionBufferList->m_bufferSize, + sessionBufferList->m_bufferSize / MEGA_BYTE, sessionBufferList->m_allocatedCount, sessionBufferList->m_maxBufferCount); StringBufferAppend(stringBuffer, "%*sBit-set: { ", indent + PRINT_REPORT_INDENT, ""); @@ -159,8 +160,8 @@ extern "C" void MemSessionLargeBufferListDump(void* arg) MOT::StringBufferApply([list](MOT::StringBuffer* stringBuffer) { MOT::MemSessionLargeBufferListToString(0, "Debug Dump", list, stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -170,12 +171,13 @@ extern "C" int MemSessionLargeBufferListAnalyze(void* list, void* buffer) MOT::MemSessionLargeBufferList* sessionBufferList = (MOT::MemSessionLargeBufferList*)list; if (((uint8_t*)buffer) >= ((uint8_t*)sessionBufferList->m_bufferList)) { uint64_t bufferOffset = (uint64_t)(((uint8_t*)buffer) - ((uint8_t*)sessionBufferList->m_bufferList)); - uint32_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; + uint64_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; if (bufferIndex < sessionBufferList->m_maxBufferCount) { MOT::MemSessionLargeBufferHeader* bufferHeader = (MOT::MemSessionLargeBufferHeader*)(sessionBufferList->m_bufferHeaderList + bufferIndex); - fprintf(stderr, - "Object %p found in session buffer list of %u bytes buffers at index %u, with real size %" PRIu64 "\n", + (void)fprintf(stderr, + "Object %p found in session buffer list of %" PRIu64 " bytes buffers at index %" PRIu64 ", with real " + "size %" PRIu64 "\n", buffer, sessionBufferList->m_bufferSize, bufferIndex, diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.h b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.h index 10f91cb07..b4c7adc25 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.h +++ b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_list.h @@ -31,7 +31,7 @@ #include "utilities.h" #include "string_buffer.h" -#include +#include namespace MOT { /** @@ -64,43 +64,43 @@ struct PACKED MemSessionLargeBufferList { MemLock m_lock; // L1 offset [0-64] /** @var The size in bytes of each buffer in the list. */ - uint32_t m_bufferSize; // L1 offset [0-4] - - /** @var The maximum number of buffers in the list/ */ - uint32_t m_maxBufferCount; // L1 offset [4-8] + uint64_t m_bufferSize; // L1 offset [0-8] /** @var The total number of bytes requested by the user. */ - uint32_t m_requestedBytes; // L1 offset [8-12] + uint64_t m_requestedBytes; // L1 offset [8-16] + + /** @var The maximum number of buffers in the list/ */ + uint32_t m_maxBufferCount; // L1 offset [16-20] /** @var The current number of buffers allocated to the application. */ - uint32_t m_allocatedCount; // L1 offset [12-16] + uint32_t m_allocatedCount; // L1 offset [20-24] /** @var The number of words in the free bit-set array. */ - uint32_t m_freeBitsetCount; // L1 offset [16-20] + uint32_t m_freeBitsetCount; // L1 offset [24-28] /** @var Align next member offset to 8 bytes. */ - uint32_t m_padding; // L1 offset [20-24] + uint32_t m_padding; // L1 offset [28-32] /** @var The starting address of the buffer containing the flat buffer list. */ - void* m_bufferList; // L1 offset [24-32] + void* m_bufferList; // L1 offset [32-40] /** @var An array of buffer header points to all buffers in the m_bufferList respectively. */ - MemSessionLargeBufferHeader* m_bufferHeaderList; // L1 offset [32-40] + MemSessionLargeBufferHeader* m_bufferHeaderList; // L1 offset [40-48] /** * @var A bit-set array denoting which buffers are free in this buffer list. A value of "1" means * "free" and a value of "0" means "allocated". */ - uint64_t m_freeBitset[0]; // L1 offset [40-...] + uint64_t m_freeBitset[0]; // L1 offset [48-...] }; /** @struct MemSessionLargeBufferStats */ struct PACKED MemSessionLargeBufferStats { /** @var The total number of bytes requested by the user. */ - uint32_t m_requestedBytes; + uint64_t m_requestedBytes; /** @var The total number of bytes given to the user. */ - uint32_t m_allocatedBytes; + uint64_t m_allocatedBytes; }; /** @@ -136,17 +136,18 @@ extern int MemSessionLargeBufferListInit( */ inline void MemSessionLargeBufferListDestroy(MemSessionLargeBufferList* sessionBufferList) { - MemLockDestroy(&sessionBufferList->m_lock); + (void)MemLockDestroy(&sessionBufferList->m_lock); } /** - * @brief Handles double buffer free. Dumps all relevant information and aborts. + * @brief Handles invalid or double buffer free. Dumps all relevant information and aborts. * @param sessionBufferList The faulting session large buffer list. * @param buffer The faulting buffer. * @param bufferIndex The faulting buffer index. + * @param invalidType "INVALID" or "DOUBLE" */ -extern void MemSessionLargeBufferListOnDoubleFree( - MemSessionLargeBufferList* sessionBufferList, void* buffer, uint32_t bufferIndex); +extern void MemSessionLargeBufferListOnInvalidFree( + MemSessionLargeBufferList* sessionBufferList, void* buffer, uint64_t bufferIndex, const char* invalidType); /** * @brief Allocates a buffer from a session large buffer list. @@ -163,7 +164,7 @@ inline MemSessionLargeBufferHeader* MemSessionLargeBufferListAlloc( for (uint32_t i = 0; i < sessionBufferList->m_freeBitsetCount; ++i) { if (sessionBufferList->m_freeBitset[i] != 0) { uint64_t bitIndex = __builtin_clzll(sessionBufferList->m_freeBitset[i]); - uint64_t freeBufferIndex = (i << 6) + bitIndex; + uint64_t freeBufferIndex = ((uint64_t)i << 6) + bitIndex; if (freeBufferIndex < sessionBufferList->m_maxBufferCount) { // guard against extreme case: max_buffer_count is not a // multiple of 64 @@ -203,12 +204,18 @@ inline void MemSessionLargeBufferListFree( { void* buffer = bufferHeader->m_buffer; uint64_t bufferOffset = (uint64_t)(((uint8_t*)buffer) - ((uint8_t*)sessionBufferList->m_bufferList)); - uint32_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; - uint32_t slot = bufferIndex / 64; + uint64_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; + if (bufferIndex >= sessionBufferList->m_maxBufferCount) { + MemSessionLargeBufferListOnInvalidFree(sessionBufferList, buffer, bufferIndex, "INVALID"); + } + uint64_t slot = bufferIndex / 64; uint32_t index = bufferIndex % 64; + if (slot >= sessionBufferList->m_freeBitsetCount) { + MemSessionLargeBufferListOnInvalidFree(sessionBufferList, buffer, bufferIndex, "INVALID"); + } MemLockAcquire(&sessionBufferList->m_lock); if (sessionBufferList->m_freeBitset[slot] & (((uint64_t)1) << (63 - index))) { - MemSessionLargeBufferListOnDoubleFree(sessionBufferList, buffer, bufferIndex); + MemSessionLargeBufferListOnInvalidFree(sessionBufferList, buffer, bufferIndex, "DOUBLE"); } sessionBufferList->m_freeBitset[slot] |= (((uint64_t)1) << (63 - index)); MOT_ASSERT(sessionBufferList->m_allocatedCount > 0); @@ -235,7 +242,7 @@ inline int MemSessionLargeBufferListGetIndex(MemSessionLargeBufferList* sessionB int result = -1; if (((uint8_t*)buffer) >= ((uint8_t*)sessionBufferList->m_bufferList)) { uint64_t bufferOffset = (uint64_t)(((uint8_t*)buffer) - ((uint8_t*)sessionBufferList->m_bufferList)); - uint32_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; + uint64_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; if (bufferIndex < sessionBufferList->m_maxBufferCount) { result = (int)bufferIndex; } @@ -254,7 +261,7 @@ inline uint64_t MemSessionLargeBufferListGetRealObjectSize(MemSessionLargeBuffer uint64_t result = 0; if (((uint8_t*)buffer) >= ((uint8_t*)sessionBufferList->m_bufferList)) { uint64_t bufferOffset = (uint64_t)(((uint8_t*)buffer) - ((uint8_t*)sessionBufferList->m_bufferList)); - uint32_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; + uint64_t bufferIndex = bufferOffset / sessionBufferList->m_bufferSize; if (bufferIndex < sessionBufferList->m_maxBufferCount) { MemSessionLargeBufferHeader* bufferHeader = (MemSessionLargeBufferHeader*)(sessionBufferList + bufferIndex); result = bufferHeader->m_realObjectSize; diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_pool.cpp b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_pool.cpp index a1e3bb1dd..9dc6ad02a 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_pool.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_pool.cpp @@ -176,11 +176,12 @@ extern void MemSessionLargeBufferPoolFree( MemSessionLargeBufferPool* bufferPool, void* buffer, MemSessionLargeBufferHeader** bufferHeaderList) { // locate the proper list + bool bufferFound = false; MOT_LOG_DEBUG("Freeing large buffer %p", buffer); for (uint32_t i = 0; i < bufferPool->m_bufferListCount; ++i) { int bufferIndex = MemSessionLargeBufferListGetIndex(bufferPool->m_bufferLists[i], buffer); if (bufferIndex != -1) { - MOT_LOG_DEBUG("Freeing large buffer %p into buffer list %u (real size %u)", + MOT_LOG_DEBUG("Freeing large buffer %p into buffer list %u (real size %" PRIu64 ")", buffer, i, bufferPool->m_bufferLists[i]->m_bufferSize); @@ -188,9 +189,14 @@ extern void MemSessionLargeBufferPoolFree( (MemSessionLargeBufferHeader*)(bufferPool->m_bufferLists[i]->m_bufferHeaderList + bufferIndex); MemSessionUnrecordLargeBuffer(bufferHeader, bufferHeaderList); MemSessionLargeBufferListFree(bufferPool->m_bufferLists[i], bufferHeader); + bufferFound = true; break; } } + if (!bufferFound) { + MOT_LOG_PANIC("Free of invalid session large buffer %p", buffer); + MOTAbort(buffer); + } } extern void* MemSessionLargeBufferPoolRealloc(MemSessionLargeBufferPool* bufferPool, void* buffer, @@ -207,11 +213,37 @@ extern void* MemSessionLargeBufferPoolRealloc(MemSessionLargeBufferPool* bufferP MemSessionLargeBufferHeader* bufferHeader = (MemSessionLargeBufferHeader*)(bufferPool->m_bufferLists[i]->m_bufferHeaderList + (uint32_t)bufferIndex); + uint64_t objectSizeBytes = bufferHeader->m_realObjectSize; if (bufferPool->m_bufferLists[i]->m_bufferSize >= newSizeBytes) { // new size fits in existing buffer, so just update real size, and return current buffer bufferHeader->m_realObjectSize = newSizeBytes; newBuffer = buffer; MOT_LOG_DEBUG("Large buffer %p is big enough for reallocation", buffer); + + // attention: when object is reallocated in-place we treat flags a bit differently, but achieve the + // same effect, so MEM_REALLOC_COPY has no effect + if (flags == MEM_REALLOC_ZERO) { + erc = memset_s(newBuffer, newSizeBytes, 0, newSizeBytes); + securec_check(erc, "\0", "\0"); + } else if (flags == MEM_REALLOC_COPY_ZERO) { + // attention: new size may be smaller + if (newSizeBytes > objectSizeBytes) { + erc = memset_s(((char*)newBuffer) + objectSizeBytes, + newSizeBytes - objectSizeBytes, + 0, + newSizeBytes - objectSizeBytes); + securec_check(erc, "\0", "\0"); + } else { +#ifdef MOT_DEBUG + // set dead land pattern in the reduced tail of the object + erc = memset_s(((char*)newBuffer) + newSizeBytes, + bufferPool->m_bufferLists[i]->m_bufferSize - newSizeBytes, + MEM_DEAD_LAND, + objectSizeBytes - newSizeBytes); + securec_check(erc, "\0", "\0"); +#endif + } + } } else { // new size does not fit in existing buffer, so allocate new buffer, and copy/zero data if required and // free old buffer @@ -219,7 +251,6 @@ extern void* MemSessionLargeBufferPoolRealloc(MemSessionLargeBufferPool* bufferP newBuffer = MemSessionLargeBufferPoolAlloc(bufferPool, newSizeBytes, bufferHeaderList); if (newBuffer != nullptr) { MOT_LOG_DEBUG("Reallocated buffer %p into %p", buffer, newBuffer); - uint64_t objectSizeBytes = bufferHeader->m_realObjectSize; if (flags == MEM_REALLOC_COPY) { // attention: new size may be smaller than object size erc = memcpy_s(newBuffer, newSizeBytes, buffer, std::min(newSizeBytes, objectSizeBytes)); @@ -287,13 +318,19 @@ static void MemSessionUnrecordLargeBuffer( extern uint64_t MemSessionLargeBufferPoolGetObjectSize(MemSessionLargeBufferPool* bufferPool, void* buffer) { uint64_t result = 0; + bool objectFound = false; for (uint32_t i = 0; i < bufferPool->m_bufferListCount; ++i) { int bufferIndex = MemSessionLargeBufferListGetIndex(bufferPool->m_bufferLists[i], buffer); if (bufferIndex != -1) { result = MemSessionLargeBufferListGetRealObjectSize(bufferPool->m_bufferLists[i], buffer); + objectFound = true; break; } } + if (!objectFound) { + MOT_LOG_PANIC("Failed to get real size of invalid session large buffer %p", buffer); + MOTAbort(buffer); + } return result; } @@ -406,9 +443,17 @@ static int InitPoolLists(MemSessionLargeBufferPool* bufferPool) // allocate array of lists bufferPool->m_bufferListCount = ComputeBufferListCount(bufferPool->m_maxObjectSizeBytes); - MOT_LOG_TRACE("Found %u buffer lists according to max object size %" PRIu64, + MOT_LOG_TRACE("Found %u buffer lists according to max object size %" PRIu64 " bytes", bufferPool->m_bufferListCount, bufferPool->m_maxObjectSizeBytes); + if (bufferPool->m_bufferListCount == 0) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Session Large Buffer Pool Initialization", + "Failed to compute list count for object size %" PRIu64 " bytes", + bufferPool->m_maxObjectSizeBytes); + return MOT_ERROR_INTERNAL; + } + uint64_t allocSize = sizeof(MemSessionLargeBufferList*) * bufferPool->m_bufferListCount; bufferPool->m_bufferLists = (MemSessionLargeBufferList**)MemNumaAllocLocal(allocSize, bufferPool->m_node); if (unlikely(bufferPool->m_bufferLists == nullptr)) { @@ -519,8 +564,8 @@ extern "C" void MemSessionLargeBufferPoolDump(void* arg) MOT::StringBufferApply([pool](MOT::StringBuffer* stringBuffer) { MOT::MemSessionLargeBufferPoolToString(0, "Debug Dump", pool, stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } @@ -530,7 +575,7 @@ extern "C" int MemSessionLargeBufferPoolAnalyze(void* pool, void* buffer) MOT::MemSessionLargeBufferPool* bufferPool = (MOT::MemSessionLargeBufferPool*)pool; for (uint32_t i = 0; i < bufferPool->m_bufferListCount; ++i) { if (MemSessionLargeBufferListAnalyze(bufferPool->m_bufferLists[i], buffer)) { - fprintf( + (void)fprintf( stderr, "Buffer %p found in session large buffer list %u on node %d\n", buffer, i, bufferPool->m_node); found = 1; break; diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.cpp b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.cpp index 68e45bb67..661ca9f25 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.cpp @@ -43,12 +43,18 @@ extern int MemSessionLargeBufferStoreInit() MOT_LOG_TRACE("Initializing Session Large Buffer Store"); uint64_t storeSizeBytes = g_memGlobalCfg.m_sessionLargeBufferStoreSizeMb * MEGA_BYTE; if (storeSizeBytes == 0) { // pool disabled + MOT_LOG_TRACE("Session large buffer stored is disabled"); return 0; } + MOT_LOG_TRACE("Using store size %" PRIu64 " bytes", storeSizeBytes); uint64_t maxObjectSizeBytes = g_memGlobalCfg.m_sessionLargeBufferStoreMaxObjectSizeMb * MEGA_BYTE; uint64_t poolSizeBytes = storeSizeBytes / g_memGlobalCfg.m_nodeCount; + MOT_LOG_TRACE( + "Using pool size %" PRIu64 " bytes, according to %u nodes", poolSizeBytes, g_memGlobalCfg.m_nodeCount); + MOT_LOG_TRACE("Using configured max object size %" PRIu64 " bytes", maxObjectSizeBytes); g_memSessionLargeBufferMaxObjectSizeBytes = ComputeRealMaxObjectSize(poolSizeBytes, maxObjectSizeBytes); + MOT_LOG_TRACE("Using computed max object size %" PRIu64 " bytes", g_memSessionLargeBufferMaxObjectSizeBytes); g_globalPools = (MemSessionLargeBufferPool**)calloc(g_memGlobalCfg.m_nodeCount, sizeof(MemSessionLargeBufferPool*)); if (unlikely(g_globalPools == nullptr)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, @@ -167,8 +173,8 @@ extern "C" void MemSessionLargeBufferDump() if (MOT::g_globalPools != nullptr) { MOT::StringBufferApply([](MOT::StringBuffer* stringBuffer) { MOT::MemSessionLargeBufferToString(0, "Debug Dump", stringBuffer, MOT::MEM_REPORT_DETAILED); - fprintf(stderr, "%s", stringBuffer->m_buffer); - fflush(stderr); + (void)fprintf(stderr, "%s", stringBuffer->m_buffer); + (void)fflush(stderr); }); } } @@ -177,7 +183,7 @@ extern "C" int MemSessionLargeBufferStoreAnalyze(void* buffer) { if (MOT::g_globalPools != nullptr) { for (uint32_t node = 0; node < MOT::g_memGlobalCfg.m_nodeCount; ++node) { - fprintf(stderr, "Searching buffer %p in session large buffer pool %u...\n", buffer, node); + (void)fprintf(stderr, "Searching buffer %p in session large buffer pool %u...\n", buffer, node); if (MOT::g_globalPools[node]) { if (MemSessionLargeBufferPoolAnalyze(MOT::g_globalPools[node], buffer)) { return 1; diff --git a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.h b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.h index 6491d8c8b..12609adf8 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.h +++ b/src/gausskernel/storage/mot/core/memory/mm_session_large_buffer_store.h @@ -30,7 +30,7 @@ #include "string_buffer.h" #include "mm_session_large_buffer_pool.h" -#include +#include namespace MOT { /** diff --git a/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.cpp b/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.cpp index 1782ea8f7..fa20d37dc 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.cpp +++ b/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.cpp @@ -31,7 +31,7 @@ #include "mot_atomic_ops.h" #include "utilities.h" -#include +#include namespace MOT { DECLARE_LOGGER(VirtualHugeChunkHeader, Memory) @@ -127,13 +127,13 @@ extern void MemVirtualHugeChunkHeaderDestroy() if (g_vhcInit) { // free all headers and lock FreeHeaders(g_globalFreeHeaders); - MemLockDestroy(&g_globalHeadersLock); + (void)MemLockDestroy(&g_globalHeadersLock); for (uint32_t i = 0; i < g_memGlobalCfg.m_nodeCount; ++i) { // free all headers and locks FreeHeaders(g_localFreeHeaders[i]); if (g_localHeadersLock[i] != nullptr) { - MemLockDestroy(g_localHeadersLock[i]); + (void)MemLockDestroy(g_localHeadersLock[i]); MemNumaFreeLocal(g_localHeadersLock[i], sizeof(MemLock), i); g_localHeadersLock[i] = nullptr; } diff --git a/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.h b/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.h index 6a6ab70c0..16a501a8c 100644 --- a/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.h +++ b/src/gausskernel/storage/mot/core/memory/mm_virtual_huge_chunk.h @@ -27,8 +27,8 @@ #include "mm_chunk_type.h" #include "mm_def.h" -#include -#include +#include +#include namespace MOT { struct PACKED MemVirtualHugeChunkHeader { diff --git a/src/gausskernel/storage/mot/core/memory/object_pool.cpp b/src/gausskernel/storage/mot/core/memory/object_pool.cpp index 3c456da10..d83092e56 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool.cpp +++ b/src/gausskernel/storage/mot/core/memory/object_pool.cpp @@ -23,20 +23,36 @@ */ #include "object_pool.h" +#include "object_pool_compact.h" namespace MOT { IMPLEMENT_CLASS_LOGGER(SlabAllocator, Memory) +void SlabAllocator::Compact() +{ + char prefix[256]; + for (int i = m_minBin; i <= m_maxBin; i++) { + if (m_bins[i] != nullptr) { + errno_t erc = snprintf_s(prefix, sizeof(prefix), sizeof(prefix) - 1, "Slab (type %d)", i); + securec_check_ss(erc, "\0", "\0"); + prefix[erc] = 0; + CompactHandler chBin(m_bins[i], prefix); + chBin.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chBin.EndCompaction(); + } + } +} + PoolStatsSt* SlabAllocator::GetStats() { - PoolStatsSt* stats = (PoolStatsSt*)calloc(SLUB_MAX_BIN + 1, sizeof(PoolStatsSt)); + PoolStatsSt* stats = (PoolStatsSt*)calloc(SLAB_MAX_BIN + 1, sizeof(PoolStatsSt)); if (stats == NULL) { MOT_LOG_ERROR("Failed to allocate memory for stats."); return NULL; } - for (int i = 0; i <= SLUB_MAX_BIN; i++) { + for (int i = 0; i <= SLAB_MAX_BIN; i++) { if (m_bins[i] != NULL) { stats[i].m_type = PoolStatsT::POOL_STATS_ALL; m_bins[i]->GetStats(stats[i]); @@ -61,7 +77,7 @@ void SlabAllocator::GetSize(uint64_t& size, uint64_t& netto) return; } - for (int i = 0; i <= SLUB_MAX_BIN; i++) { + for (int i = 0; i <= SLAB_MAX_BIN; i++) { if (m_bins[i] != NULL) { size += stats[i].m_poolCount * stats[i].m_poolGrossSize; netto += (stats[i].m_totalObjCount - stats[i].m_freeObjCount) * stats[i].m_objSize; @@ -71,13 +87,19 @@ void SlabAllocator::GetSize(uint64_t& size, uint64_t& netto) FreeStats(stats); } -void SlabAllocator::PrintStats(PoolStatsSt* stats, const char* prefix, LogLevel level) +void SlabAllocator::PrintSize(uint64_t& size, uint64_t& netto, const char* prefix) +{ + GetSize(size, netto); + MOT_LOG_INFO("%s memory size - Gross: %lu, NetTotal: %lu", prefix, size, netto); +} + +void SlabAllocator::PrintStats(PoolStatsSt* stats, const char* prefix, LogLevel level) const { if (stats == NULL) { return; } - for (int i = 0; i <= SLUB_MAX_BIN; i++) { + for (int i = 0; i <= SLAB_MAX_BIN; i++) { if (m_bins[i] != NULL) { m_bins[i]->PrintStats(stats[i], prefix, level); } diff --git a/src/gausskernel/storage/mot/core/memory/object_pool.h b/src/gausskernel/storage/mot/core/memory/object_pool.h index e0f420356..f4f5c1f54 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool.h +++ b/src/gausskernel/storage/mot/core/memory/object_pool.h @@ -34,15 +34,20 @@ public: { m_nextFree = nullptr; m_objList = nullptr; +#ifdef ENABLE_MEMORY_CHECK + m_size = MEMCHECK_OBJPOOL_SIZE; + m_actualSize = ALIGN_N(sz + MEMCHECK_METAINFO_SIZE, align); +#else m_size = ALIGN_N(sz + OBJ_INDEX_SIZE, align); +#endif m_oixOffset = m_size - 1; - m_type = ObjAllocInterface::CalcBufferClass(sz); + m_type = ObjAllocInterface::CalcBufferClass(m_size); }; ~LocalObjPool() override { Print("Local"); - }; + } bool Initialize() override { @@ -63,17 +68,28 @@ public: return result; } - inline void Release(void* ptr) override + void Release(void* ptr) override { PoolAllocStateT state = PAS_NONE; OBJ_RELEASE_START(ptr, m_size); +#ifdef ENABLE_MEMORY_CHECK + errno_t erc = memset_s(ptr, m_actualSize - MEMCHECK_METAINFO_SIZE, 'Z', m_actualSize - MEMCHECK_METAINFO_SIZE); + securec_check(erc, "\0", "\0"); +#elif defined(DEBUG) + errno_t erc = memset_s(ptr, m_oixOffset, 'Z', m_oixOffset); + securec_check(erc, "\0", "\0"); +#endif op->ReleaseNoLock(oix, &state); - if (state == PAS_FIRST) + if (state == PAS_FIRST) { PUSH_NOLOCK(m_nextFree, op); + } +#ifdef ENABLE_MEMORY_CHECK + free(((uint8_t*)ptr) - MEMCHECK_METAINFO_SIZE); +#endif } - inline void* Alloc() override + void* Alloc() override { PoolAllocStateT state = PAS_NONE; void* data = nullptr; @@ -92,8 +108,9 @@ public: m_nextFree->AllocNoLock(&data, &state); - if (state == PAS_EMPTY) + if (state == PAS_EMPTY) { POP_NOLOCK(m_nextFree); + } return data; } @@ -103,6 +120,11 @@ public: return; } + void ClearAllThreadCache() override + { + return; + } + void ClearFreeCache() override { ObjPoolPtr prev = m_nextFree; @@ -137,23 +159,29 @@ typedef struct PACKED __ThreadAOP { class GlobalObjPool : public ObjAllocInterface { public: ThreadAOP m_threadAOP[MAX_THREAD_COUNT]; - + pthread_mutex_t m_thrLock; GlobalObjPool(uint16_t sz, uint8_t align) : ObjAllocInterface(true) { m_objList = nullptr; m_nextFree = nullptr; +#ifdef ENABLE_MEMORY_CHECK + m_size = MEMCHECK_OBJPOOL_SIZE; + m_actualSize = ALIGN_N(sz + MEMCHECK_METAINFO_SIZE, align); +#else m_size = ALIGN_N(sz + OBJ_INDEX_SIZE, align); +#endif m_oixOffset = m_size - 1; - m_type = ObjAllocInterface::CalcBufferClass(sz); + m_type = ObjAllocInterface::CalcBufferClass(m_size); errno_t erc = memset_s(m_threadAOP, MAX_THREAD_COUNT * sizeof(ThreadAOP), 0, MAX_THREAD_COUNT * sizeof(ThreadAOP)); securec_check(erc, "\0", "\0"); + m_thrLock = PTHREAD_MUTEX_INITIALIZER; }; ~GlobalObjPool() override { Print("Global"); - }; + } bool Initialize() override { @@ -182,7 +210,7 @@ public: if (tmp.Get() != nullptr) { tmp->m_owner = G_THREAD_ID; tmp->m_objNext = nullptr; - MEMORY_BARRIER; + COMPILER_BARRIER; return tmp; } } @@ -190,7 +218,7 @@ public: ObjPool* op = ObjPool::GetObjPool(m_size, this, m_type, true); if (op != nullptr) { - ADD_TO_LIST(m_objList, op); + ADD_TO_LIST(m_listLock, m_objList, op); op->m_owner = G_THREAD_ID; tmp = op; } @@ -204,116 +232,148 @@ public: PUSH(m_nextFree, op); } - inline void Release(void* ptr) override + void Release(void* ptr) override { PoolAllocStateT state = PAS_NONE; ThreadAOP* t = &m_threadAOP[G_THREAD_ID]; OBJ_RELEASE_START(ptr, m_size); +#ifdef ENABLE_MEMORY_CHECK + errno_t erc = memset_s(ptr, m_actualSize - MEMCHECK_METAINFO_SIZE, 'Z', m_actualSize - MEMCHECK_METAINFO_SIZE); + securec_check(erc, "\0", "\0"); +#elif defined(DEBUG) + errno_t erc = memset_s(ptr, m_oixOffset, 'Z', m_oixOffset); + securec_check(erc, "\0", "\0"); +#endif op->Release(oix, &state); if (state == PAS_FIRST) { - if (t->m_nextFree.Get() == nullptr) { - op->m_owner = G_THREAD_ID; - t->m_nextFree = op; - } else { - op->m_owner = -1; - MEMORY_BARRIER; - PUSH(m_nextFree, op); - } + op->m_owner = G_THREAD_ID; + PUSH_NOLOCK(t->m_nextFree, op); } +#ifdef ENABLE_MEMORY_CHECK + free(((uint8_t*)ptr) - MEMCHECK_METAINFO_SIZE); +#endif } - inline void* Alloc() override + void* Alloc() override { PoolAllocStateT state = PAS_NONE; void* data = nullptr; - + ObjPoolPtr opNext; ThreadAOP* t = &m_threadAOP[G_THREAD_ID]; if (t->m_nextFree.Get() == nullptr) { t->m_nextFree = Reserve(); if (unlikely(t->m_nextFree.Get() == nullptr)) { // out of memory - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "N/A", "Failed to reserve sub-pool in thread %u", (unsigned)G_THREAD_ID); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "N/A", "Failed to reserve sub-pool in thread %u" PRId16, G_THREAD_ID); return nullptr; } } + opNext = t->m_nextFree->m_objNext; t->m_nextFree->Alloc(&data, &state); if (state == PAS_EMPTY) { - t->m_nextFree = nullptr; + t->m_nextFree = opNext; } return data; } void ClearThreadCache() override { + (void)pthread_mutex_lock(&m_thrLock); ObjPoolPtr op = m_threadAOP[G_THREAD_ID].m_nextFree; - + ObjPoolPtr head = op; + ObjPoolPtr tail = op; while (op.Get() != nullptr) { - ObjPoolPtr tmp = op->m_objNext; - Unreserve(op); - op = tmp; - } - - m_threadAOP[G_THREAD_ID].m_nextFree = nullptr; - } - - void ClearFreeCache() override - { - ObjPoolPtr orig = nullptr; - ObjPoolPtr p = nullptr; - do { - orig = m_nextFree; - } while (!CAS(m_nextFree, orig, p)); - ObjPoolPtr prev = orig; - p = (orig.Get() != nullptr ? orig->m_objNext : nullptr); - - while (p.Get() != nullptr) { - if (p->m_freeCount == p->m_totalCount) { - ObjPool* op = p.Get(); - - prev->m_objNext = p->m_objNext; - p = p->m_objNext; - - DEL_FROM_LIST(m_listLock, m_objList, op); - ObjPool::DelObjPool(op, m_type, m_global); + if (op->m_freeCount == op->m_totalCount) { + ObjPool* p = op.Get(); + if (head.Get() == op.Get()) { + head = op->m_objNext; + tail = op->m_objNext; + } else { + tail->m_objNext = op->m_objNext; + } + op = op->m_objNext; + DEL_FROM_LIST(m_listLock, m_objList, p); + ObjPool::DelObjPool(p, m_type, m_global); } else { - ++(prev->m_objNext); - prev = p; - p = p->m_objNext; + tail = op; + op = op->m_objNext; } } + m_threadAOP[G_THREAD_ID].m_nextFree = nullptr; - if (unlikely(prev.Get() != nullptr)) { - ++orig; - do { - prev->m_objNext = m_nextFree; - } while (!CAS(m_nextFree, prev->m_objNext, orig)); + if (head.Get() == nullptr) { + (void)pthread_mutex_unlock(&m_thrLock); + return; } + if (tail.Get() == nullptr) { + tail = head; + } + do { + tail->m_objNext = m_nextFree; + } while (!CAS(m_nextFree, tail->m_objNext, head)); + + (void)pthread_mutex_unlock(&m_thrLock); + } + void ClearAllThreadCache() override + { + for (int i = 0; i < MAX_THREAD_COUNT; i++) { + ObjPoolPtr op = m_threadAOP[i].m_nextFree; + + while (op.Get() != nullptr) { + ObjPoolPtr tmp = op->m_objNext; + Unreserve(op); + op = tmp; + } + + m_threadAOP[i].m_nextFree = nullptr; + } + m_nextFree = nullptr; + } + void ClearFreeCache() override + { + ObjPoolPtr op = m_threadAOP[G_THREAD_ID].m_nextFree; + ObjPoolPtr head = op; + ObjPoolPtr prev = op; + while (op.Get() != nullptr) { + if (op->m_freeCount == op->m_totalCount) { + ObjPool* p = op.Get(); + if (head.Get() == op.Get()) { + head = op->m_objNext; + prev = op->m_objNext; + } else { + prev->m_objNext = op->m_objNext; + } + op = op->m_objNext; + DEL_FROM_LIST(m_listLock, m_objList, p); + ObjPool::DelObjPool(p, m_type, m_global); + } else { + prev = op; + op = op->m_objNext; + } + } + m_threadAOP[G_THREAD_ID].m_nextFree = head; } }; -#define SLUB_MAX_BIN 15 // up to 32KB -#define SLUB_MIN_BIN 3 - class SlabAllocator { public: SlabAllocator(int min, int max, bool local) : m_isLocal(local) { m_isInitialized = true; - if (min < SLUB_MIN_BIN) { - m_minBin = SLUB_MIN_BIN; + if (min < SLAB_MIN_BIN) { + m_minBin = SLAB_MIN_BIN; } else { m_minBin = min; } - if (max > SLUB_MAX_BIN) { + if (max > SLAB_MAX_BIN) { m_isInitialized = false; MOT_LOG_PANIC( - "Max allowed slub size is 64KB (bin 16), failed to create slab allocator for min: %d, max: %d", + "Max allowed slub size is 32KB (bin 15), failed to create slab allocator for min: %d, max: %d", min, max); MOTAbort(); @@ -327,12 +387,12 @@ public: MOTAbort(); } - for (int i = 0; i <= SLUB_MAX_BIN; i++) { + for (int i = 0; i <= SLAB_MAX_BIN; i++) { m_bins[i] = nullptr; } for (int i = min; i <= m_maxBin; i++) { - int s = (1 << i) - 1; + int s = (1U << i) - 1; m_bins[i] = ObjAllocInterface::GetObjPool(s, local); if (m_bins[i] == nullptr) { m_isInitialized = false; @@ -342,7 +402,7 @@ public: ~SlabAllocator() { - for (int i = 0; i <= SLUB_MAX_BIN; i++) { + for (int i = 0; i <= SLAB_MAX_BIN; i++) { if (m_bins[i] != nullptr) { ObjAllocInterface::FreeObjPool(&m_bins[i]); } @@ -353,7 +413,7 @@ public: { bool result = true; for (int i = m_minBin; i <= m_maxBin; i++) { - int s = (1 << i) - 1; + int s = (1U << i) - 1; m_bins[i] = ObjAllocInterface::GetObjPool(s, m_isLocal); if (m_bins[i] == nullptr) { result = false; @@ -368,7 +428,7 @@ public: { int b = (__builtin_clz(size) ^ 31) + 1; if (unlikely(b > m_maxBin)) { - MOT_LOG_PANIC("Unsupported size %d, max allowed size %d", size, (1 << m_maxBin) - 1); + MOT_LOG_PANIC("Unsupported size %d, max allowed size %d", size, (1U << m_maxBin) - 1); MOTAbort(); } @@ -389,32 +449,30 @@ public: inline void* Alloc(int& size) { int i = CalcBinNum(size); - size = (1 << i) - 1; + size = (1U << i) - 1; return m_bins[i]->Alloc(); } void ClearThreadCache() { - if (!m_isLocal) { - for (int i = m_minBin; i <= m_maxBin; i++) { - if (m_bins[i] != nullptr) { - m_bins[i]->ClearThreadCache(); - } + for (int i = m_minBin; i <= m_maxBin; i++) { + if (m_bins[i] != nullptr) { + m_bins[i]->ClearThreadCache(); } } } void ClearFreeCache() { - if (m_isLocal) { - for (int i = m_minBin; i <= m_maxBin; i++) { - if (m_bins[i] != nullptr) { - m_bins[i]->ClearFreeCache(); - } + for (int i = m_minBin; i <= m_maxBin; i++) { + if (m_bins[i] != nullptr) { + m_bins[i]->ClearFreeCache(); } } } + void Compact(); + inline bool IsSlabInitialized() const { return m_isInitialized; @@ -423,15 +481,19 @@ public: PoolStatsSt* GetStats(); void FreeStats(PoolStatsSt* stats); void GetSize(uint64_t& size, uint64_t& netto); - void PrintStats(PoolStatsSt* stats, const char* prefix = "", LogLevel level = LogLevel::LL_DEBUG); + void PrintSize(uint64_t& size, uint64_t& netto, const char* prefix = ""); + void PrintStats(PoolStatsSt* stats, const char* prefix = "", LogLevel level = LogLevel::LL_DEBUG) const; void Print(const char* prefix, LogLevel level = LogLevel::LL_DEBUG); + static constexpr int SLAB_MIN_BIN = 3; + static constexpr int SLAB_MAX_BIN = 15; // up to 32KB + private: bool m_isLocal; bool m_isInitialized; int m_minBin; int m_maxBin; - ObjAllocInterface* m_bins[SLUB_MAX_BIN + 1]; + ObjAllocInterface* m_bins[SLAB_MAX_BIN + 1]; DECLARE_CLASS_LOGGER(); }; diff --git a/src/gausskernel/storage/mot/core/memory/object_pool_compact.cpp b/src/gausskernel/storage/mot/core/memory/object_pool_compact.cpp index 42efb5dda..8411624e7 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool_compact.cpp +++ b/src/gausskernel/storage/mot/core/memory/object_pool_compact.cpp @@ -25,7 +25,7 @@ #include "object_pool_compact.h" namespace MOT { -addrMap_t::size_type g_addrMap_size = 1024; +static constexpr addrMap_t::size_type ADDR_MAP_SIZE = 1024; IMPLEMENT_CLASS_LOGGER(CompactHandler, System) @@ -34,7 +34,7 @@ CompactHandler::CompactHandler(ObjAllocInterface* pool, const char* prefix) : m_orig(pool), m_compactionNeeded(false), m_ctype(COMPACT_SIMPLE), - m_addrMap(1024, hashing_func(), key_equal_fn()), + m_addrMap(ADDR_MAP_SIZE, hashing_func(), key_equal_fn()), m_poolsToCompact(0), m_compactedPools(nullptr), m_curr(nullptr), @@ -99,7 +99,7 @@ void CompactHandler::EndCompaction() // add current object pool to free list if (m_curr != nullptr) { - ADD_TO_LIST(m_orig->m_objList, m_curr); + ADD_TO_LIST(m_orig->m_listLock, m_orig->m_objList, m_curr); if (m_curr->m_freeCount < m_curr->m_totalCount) { ObjPoolPtr p = m_curr; PUSH(m_orig->m_nextFree, p); diff --git a/src/gausskernel/storage/mot/core/memory/object_pool_compact.h b/src/gausskernel/storage/mot/core/memory/object_pool_compact.h index 3de9ea4a2..f903b3278 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool_compact.h +++ b/src/gausskernel/storage/mot/core/memory/object_pool_compact.h @@ -54,6 +54,10 @@ public: ~CompactHandler() { EndCompaction(); + m_curr = nullptr; + m_orig = nullptr; + m_logPrefix = nullptr; + m_compactedPools = nullptr; } /** * @brief Prepares orig for compaction, calculates fragmentation percent, initializes addrMap and set @@ -64,7 +68,7 @@ public: */ void EndCompaction(); - bool IsCompactionNeeded() + bool IsCompactionNeeded() const { return m_compactionNeeded; } @@ -72,7 +76,7 @@ public: /** @brief Reallocates the object. Allocates a new memory buffer and calls the copy constructor of T. */ template - T* CompactObj(T const* obj) + T* CompactObj(T* obj) { T* res = nullptr; @@ -103,6 +107,9 @@ public: state = PAS_NONE; obj->~T(); op->Release(oix, &state); +#ifdef ENABLE_MEMORY_CHECK + free(((uint8_t*)obj) - MEMCHECK_METAINFO_SIZE); +#endif } return res; diff --git a/src/gausskernel/storage/mot/core/memory/object_pool_impl.cpp b/src/gausskernel/storage/mot/core/memory/object_pool_impl.cpp index 4c1c23923..54932a605 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool_impl.cpp +++ b/src/gausskernel/storage/mot/core/memory/object_pool_impl.cpp @@ -22,6 +22,8 @@ * ------------------------------------------------------------------------- */ +#include + #include "object_pool_impl.h" #include "object_pool.h" @@ -69,7 +71,7 @@ ObjAllocInterface* ObjAllocInterface::GetObjPool(uint16_t size, bool local, uint return result; } -void ObjAllocInterface::FreeObjPool(ObjAllocInterface** pool) +void ObjAllocInterface::FreeObjPool(ObjAllocInterface** pool) noexcept { if (pool != NULL && *pool != NULL) { delete *pool; @@ -85,25 +87,70 @@ ObjAllocInterface::~ObjAllocInterface() p = p->m_next; ObjPool::DelObjPool(tmp, m_type, m_global); } + m_objList = nullptr; } -inline MemBufferClass ObjAllocInterface::CalcBufferClass(uint16_t size) +MemBufferClass ObjAllocInterface::CalcBufferClass(uint16_t size) { +#ifdef ENABLE_MEMORY_CHECK + int pool_size = sizeof(ObjPool) + NUM_OBJS * MEMCHECK_OBJPOOL_SIZE; +#else int pool_size = sizeof(ObjPool) + NUM_OBJS * size; +#endif // 1KB has 11 bit set (starting from less significant) // we use 32 bit int for size, so first 1 bit will 32 - __builtin_clz(pool_size) // the buffer classes start from 1KB return MemBufferClassLowerBound(pool_size); } +size_t ObjAllocInterface::CalcRequiredMemDiff(const PoolStatsSt& stats, size_t newSize, uint8_t align) const +{ + if (stats.m_poolCount == 0) { + return MIN_CHUNK_REQUIRED; + } +#ifdef ENABLE_MEMORY_CHECK + newSize = MEMCHECK_OBJPOOL_SIZE; +#else + newSize = ALIGN_N(newSize + OBJ_INDEX_SIZE, align); +#endif + MemBufferClass memType = ObjAllocInterface::CalcBufferClass(newSize); + uint64_t newPoolGrossSize = K2B((uint64_t)MemBufferClassToSizeKb(memType)); + uint16_t newObjCountInPool = (uint16_t)((newPoolGrossSize - sizeof(ObjPool)) / newSize); + uint16_t oldObjCountInPool = stats.m_totalObjCount / stats.m_poolCount; + uint32_t newPoolCount = ((stats.m_totalObjCount - stats.m_freeObjCount) / newObjCountInPool) + 1; + uint32_t newNumOf2MBChunks = (newPoolCount * newPoolGrossSize) / MEM_CHUNK_SIZE_BYTES + 1; + uint32_t oldNumOf2MBChunks = + ((stats.m_poolCount - stats.m_poolFreeCount) * stats.m_poolGrossSize) / MEM_CHUNK_SIZE_BYTES + 1; + uint32_t extraChunks = MIN_CHUNK_REQUIRED; + + MOT_LOG_INFO("old pools %lu:%lu:%lu, new pools %lu:%lu:%lu chunks %u:%u actual chunks used %u", + stats.m_poolCount, + stats.m_poolGrossSize, + oldObjCountInPool, + newPoolCount, + newPoolGrossSize, + newObjCountInPool, + oldNumOf2MBChunks, + newNumOf2MBChunks, + stats.m_usedChunkCount); + if (oldNumOf2MBChunks < stats.m_usedChunkCount) { + extraChunks += stats.m_usedChunkCount - oldNumOf2MBChunks; + } + if (newNumOf2MBChunks > oldNumOf2MBChunks) { + extraChunks += (newNumOf2MBChunks - oldNumOf2MBChunks); + } + return extraChunks; +} + void ObjAllocInterface::GetStats(PoolStatsSt& stats) { ObjPoolPtr p; - if (stats.m_type == PoolStatsT::POOL_STATS_ALL) + if (stats.m_type == PoolStatsT::POOL_STATS_ALL) { p = m_objList; - else + } else { p = m_nextFree; + } stats.m_objSize = m_size; stats.m_poolGrossSize = (1024 * MemBufferClassToSizeKb(m_type)); @@ -111,33 +158,75 @@ void ObjAllocInterface::GetStats(PoolStatsSt& stats) if (p.Get()) { stats.m_perPoolTotalCount = p->m_totalCount; stats.m_perPoolOverhead = p->m_overheadBytes; - stats.m_perPoolWaist = p->m_notUsedBytes; + stats.m_perPoolWaste = p->m_notUsedBytes; } while (p.Get() != NULL) { stats.m_poolCount++; - if (p->m_totalCount == p->m_freeCount) + if (p->m_totalCount == p->m_freeCount) { stats.m_poolFreeCount++; + } stats.m_totalObjCount += p->m_totalCount; stats.m_freeObjCount += p->m_freeCount; - if (stats.m_type == PoolStatsT::POOL_STATS_ALL) + if (stats.m_type == PoolStatsT::POOL_STATS_ALL) { p = p->m_next; - else + } else { p = p->m_objNext; + } } - if (stats.m_poolCount > 0) + if (stats.m_poolCount > 0) { stats.m_fragmentationPercent = (int16_t)(stats.m_poolFreeCount * 100 / stats.m_poolCount); - else + } else { stats.m_fragmentationPercent = 0; + } } -void ObjAllocInterface::PrintStats(PoolStatsSt& stats, const char* prefix, LogLevel level) +void ObjAllocInterface::GetStatsEx(PoolStatsSt& stats, ObjPoolAddrSet_t* poolSet) +{ + ObjPoolPtr p; + std::unordered_set chunkIdSet; + std::pair::iterator, bool> ret; + p = m_objList; + stats.m_objSize = m_size; + stats.m_poolGrossSize = (1024 * MemBufferClassToSizeKb(m_type)); + + if (p.Get()) { + stats.m_perPoolTotalCount = p->m_totalCount; + stats.m_perPoolOverhead = p->m_overheadBytes; + stats.m_perPoolWaste = p->m_notUsedBytes; + } + while (p.Get() != NULL) { + uint64_t chunkId = (uint64_t)MemRawChunkDirLookup(p.Get()); + ret = chunkIdSet.insert(chunkId); + if (ret.second == true) { + stats.m_usedChunkCount++; + } + if (poolSet != nullptr) { + (void)poolSet->insert(p.Get()); + } + stats.m_poolCount++; + if (p->m_totalCount == p->m_freeCount) { + stats.m_poolFreeCount++; + } + stats.m_totalObjCount += p->m_totalCount; + stats.m_freeObjCount += p->m_freeCount; + p = p->m_next; + } + + if (stats.m_poolCount > 0) { + stats.m_fragmentationPercent = (int16_t)(stats.m_poolFreeCount * 100 / stats.m_poolCount); + } else { + stats.m_fragmentationPercent = 0; + } +} + +void ObjAllocInterface::PrintStats(const PoolStatsSt& stats, const char* prefix, LogLevel level) const { const char* hist_str = ""; MOT_LOG(level, "%s: type: %d, size: %d, pools: %u(%u), total objects: %lu, free objects: %lu" - ", overhead: %lu, waist: %lu" + ", overhead: %lu, waste: %lu" "\n%s", prefix, m_type, @@ -147,7 +236,7 @@ void ObjAllocInterface::PrintStats(PoolStatsSt& stats, const char* prefix, LogLe stats.m_totalObjCount, stats.m_freeObjCount, stats.m_poolCount * stats.m_perPoolOverhead, - stats.m_poolCount * stats.m_perPoolWaist, + stats.m_poolCount * stats.m_perPoolWaste, hist_str); } @@ -162,4 +251,102 @@ void ObjAllocInterface::Print(const char* prefix, LogLevel level) GetStats(stats); PrintStats(stats, prefix, level); } + +ObjPool::~ObjPool() +{ +#ifdef ENABLE_MEMORY_CHECK + ObjPoolItr itr(this); + while (itr.Next() != nullptr) { + itr.ReleaseCurrent(); + } +#endif + m_next = nullptr; + m_prev = nullptr; + m_parent = nullptr; +} + +ObjPool* ObjPool::GetObjPool(uint16_t size, ObjAllocInterface* app, MemBufferClass type, bool global) +{ +#ifdef TEST_STAT_ALLOC + uint64_t start_time = GetSysClock(); +#endif +#ifndef MEM_ACTIVE + void* p = (void*)malloc((1024 * MemBufferClassToSizeKb(type))); +#else + void* p; + + if (global) { + p = MemBufferAllocGlobal(type); + } else { +#ifdef MEM_SESSION_ACTIVE + uint32_t bufferSize = 1024 * MemBufferClassToSizeKb(type); + p = MemSessionAlloc(bufferSize); + if (p) { + DetailedMemoryStatisticsProvider::GetInstance().AddLocalBuffersUsed(MOTCurrentNumaNodeId, type); + } +#else + p = MemBufferAllocLocal(type); +#endif // MEM_SESSION_ACTIVE + } +#endif // MEM_ACTIVE +#ifdef TEST_STAT_ALLOC + uint64_t end_time = GetSysClock(); + MemoryStatisticsProvider::GetInstance().AddMallocTime( + CpuCyclesLevelTime::CyclesToNanoseconds(end_time - start_time)); +#endif + ObjPool* o = nullptr; + if (p) { + o = new (p) ObjPool(size, type, app); + } else { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "N/A", + "Failed to allocate %s %s buffer for object pool", + MemBufferClassToString(type), + global ? "global" : "local"); + } + return o; +} + +void ObjPool::DelObjPool(void* ptr, MemBufferClass type, bool global) +{ + ((ObjPool*)ptr)->~ObjPool(); +#ifdef TEST_STAT_ALLOC + uint64_t start_time = GetSysClock(); +#endif +#ifndef MEM_ACTIVE + free(ptr); +#else + if (global) { + MemBufferFreeGlobal(ptr, type); + } else { +#ifdef MEM_SESSION_ACTIVE + MemSessionFree(ptr); + DetailedMemoryStatisticsProvider::GetInstance().AddLocalBuffersFreed(MOTCurrentNumaNodeId, type); +#else + MemBufferFreeLocal(ptr, type); +#endif // MEM_SESSION_ACTIVE + } +#endif // MEM_ACTIVE +#ifdef TEST_STAT_ALLOC + uint64_t end_time = GetSysClock(); + MemoryStatisticsProvider::GetInstance().AddFreeTime(CpuCyclesLevelTime::CyclesToNanoseconds(end_time - start_time)); +#endif +} + +#ifdef ENABLE_MEMORY_CHECK +void ObjPool::AllocForMemCheck(void** ret) +{ + uint8_t* tmp = (uint8_t*)(*ret); + *ret = malloc(m_parent->m_actualSize); + if (*ret == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "N/A", "Failed to allocate actual memory"); + MOTAbort(); + } + *(uint64_t*)(*ret) = (uint64_t)tmp; + *(uint64_t*)tmp = (uint64_t)(uint8_t*)(*ret); + tmp = (uint8_t*)*ret; + tmp += MEMCHECK_METAINFO_SIZE; + *ret = tmp; +} +#endif } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/object_pool_impl.h b/src/gausskernel/storage/mot/core/memory/object_pool_impl.h index fa1ec7df4..1c96d9e1b 100644 --- a/src/gausskernel/storage/mot/core/memory/object_pool_impl.h +++ b/src/gausskernel/storage/mot/core/memory/object_pool_impl.h @@ -27,10 +27,12 @@ #include #include -#include +#include #include -#include +#include #include +#include +#include #include "global.h" #include "mot_atomic_ops.h" @@ -50,110 +52,124 @@ namespace MOT { #define NOT_VALID (uint8_t)(-1) #define G_THREAD_ID ((int16_t)MOTCurrThreadId) #define OBJ_INDEX_SIZE 1 -#define MEMORY_BARRIER asm volatile("" ::: "memory"); +#define MIN_CHUNK_REQUIRED 2 +#define MEM_CHUNK_SIZE_BYTES K2B(K2B(MEM_CHUNK_SIZE_MB)) -#define OBJ_RELEASE_START_NOMARK(ptr, size) \ - uint8_t* p = (uint8_t*)ptr; \ - uint8_t* oix_ptr = (p + size - 1); \ - uint8_t oix = *oix_ptr; \ - if (oix == NOT_VALID) { \ - printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)ptr); \ - MOTAbort((void*)ptr); \ - } \ - ObjPoolPtr op = (ObjPool*)(p - sizeof(ObjPool) - oix * size); +#ifdef ENABLE_MEMORY_CHECK +#define MEMCHECK_OBJPOOL_SIZE 16 +#define MEMCHECK_METAINFO_SIZE 8 +#define OBJ_GET_REAL_PTR(ptr) (uint8_t*)(*(uint64_t*)((uint8_t*)(ptr) - MEMCHECK_METAINFO_SIZE)) +#else +#define OBJ_GET_REAL_PTR(ptr) (uint8_t*)(ptr) +#endif #define OBJ_RELEASE_MARK(ptr) (*(uint8_t*)ptr = NOT_VALID) -#define OBJ_RELEASE_START(ptr, size) \ - uint8_t* p = (uint8_t*)ptr; \ - uint8_t* oix_ptr = (p + size - 1); \ - uint8_t oix = *oix_ptr; \ - if (oix == NOT_VALID) { \ - printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)ptr); \ - MOTAbort(ptr); \ - } else { \ - if (!__sync_bool_compare_and_swap(oix_ptr, oix, NOT_VALID)) { \ - printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)ptr); \ - MOTAbort(ptr); \ - } \ - } \ - ObjPoolPtr op = (ObjPool*)(p - sizeof(ObjPool) - oix * size); +#define OBJ_RELEASE_START_NOMARK(ptr, size) \ + uint8_t* p = OBJ_GET_REAL_PTR(ptr); \ + uint8_t* oix_ptr = (p + (size) - 1); \ + uint8_t oix = *oix_ptr; \ + if (oix == NOT_VALID) { \ + (void)printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)(ptr)); \ + MOTAbort(ptr); \ + } \ + ObjPoolPtr op = (ObjPool*)((p - sizeof(ObjPool)) - static_cast(oix) * (size)); + +#define OBJ_RELEASE_START(ptr, size) \ + uint8_t* p = OBJ_GET_REAL_PTR(ptr); \ + uint8_t* oix_ptr = (p + (size) - 1); \ + uint8_t oix = *oix_ptr; \ + if (oix == NOT_VALID) { \ + (void)printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)(ptr)); \ + MOTAbort(ptr); \ + } else { \ + if (!__sync_bool_compare_and_swap(oix_ptr, oix, NOT_VALID)) { \ + (void)printf("Detected double free of pointer or corruption: 0x%lx\n", (uint64_t)(ptr)); \ + MOTAbort(ptr); \ + } \ + } \ + ObjPoolPtr op = (ObjPool*)((p - sizeof(ObjPool)) - static_cast(oix) * (size)); #define CAS(ptr, oldval, newval) \ __sync_bool_compare_and_swap((uint64_t*)&ptr, *(uint64_t*)&(oldval), *(uint64_t*)&newval) -#define PUSH_NOLOCK(list, obj) \ - { \ - obj->m_objNext = list; \ - list = obj; \ - } +#define PUSH_NOLOCK(list, obj) \ + do { \ + (obj)->m_objNext = list; \ + list = obj; \ + } while (0) -#define POP_NOLOCK(list) \ - { \ - if (list.Get() != nullptr) { \ - list = (list)->m_objNext; \ - } \ - } +#define POP_NOLOCK(list) \ + do { \ + if ((list).Get() != nullptr) { \ + list = (list)->m_objNext; \ + } \ + } while (0) -#define PUSH(list, obj) \ - { \ - ++obj; \ - do { \ - obj->m_objNext = list; \ - } while (!CAS(list, obj->m_objNext, obj)); \ - } +#define PUSH(list, obj) \ + do { \ + ++(obj); \ + do { \ + (obj)->m_objNext = list; \ + } while (!CAS(list, (obj)->m_objNext, obj)); \ + } while (0) -#define POP(list, obj) \ - { \ - do { \ - obj = list; \ - if (obj.Get() == nullptr) \ - break; \ - } while (!CAS(list, obj, obj->m_objNext)); \ - } +#define POP(list, obj) \ + do { \ + do { \ + obj = list; \ + if ((obj).Get() == nullptr) { \ + break; \ + } \ + } while (!CAS(list, obj, (obj)->m_objNext)); \ + } while (0) #define ADD_TO_LIST_NOLOCK(list, obj) \ - { \ - obj->m_next = list; \ - if (list != nullptr) \ - list->m_prev = obj; \ - list = obj; \ - } + do { \ + (obj)->m_next = list; \ + if ((list) != nullptr) { \ + (list)->m_prev = obj; \ + } \ + (list) = obj; \ + } while (0) -#define DEL_FROM_LIST_NOLOCK(list, obj) \ - { \ - if (obj->m_prev != nullptr) { \ - obj->m_prev->m_next = obj->m_next; \ - } \ - if (obj->m_next != nullptr) { \ - obj->m_next->m_prev = obj->m_prev; \ - } \ - } +#define DEL_FROM_LIST_NOLOCK(list, obj) \ + do { \ + if ((obj)->m_prev != nullptr) { \ + (obj)->m_prev->m_next = (obj)->m_next; \ + } \ + if ((obj)->m_next != nullptr) { \ + (obj)->m_next->m_prev = (obj)->m_prev; \ + } \ + } while (0) -#define ADD_TO_LIST(list, obj) \ - { \ - do { \ - obj->m_next = list; \ - } while (!CAS(list, obj->m_next, obj)); \ - if (likely(obj->m_next != nullptr)) \ - obj->m_next->m_prev = obj; \ - } +#define ADD_TO_LIST(locker, list, obj) \ + do { \ + locker.lock(); \ + do { \ + (obj)->m_next = list; \ + } while (!CAS(list, (obj)->m_next, obj)); \ + if (likely((obj)->m_next != nullptr)) { \ + (obj)->m_next->m_prev = obj; \ + } \ + locker.unlock(); \ + } while (0) -#define DEL_FROM_LIST(locker, list, obj) \ - { \ - do { \ - if (list == obj) { \ - if (CAS(list, obj, obj->m_next)) { \ - obj->m_next = nullptr; \ - obj->m_prev = nullptr; \ - break; \ - } \ - } \ - locker.lock(); \ - DEL_FROM_LIST_NOLOCK(list, obj) \ - locker.unlock(); \ - } while (0); \ - } +#define DEL_FROM_LIST(locker, list, obj) \ + do { \ + locker.lock(); \ + do { \ + if ((list) == (obj)) { \ + if (CAS(list, obj, (obj)->m_next)) { \ + (obj)->m_next = nullptr; \ + (obj)->m_prev = nullptr; \ + break; \ + } \ + } \ + DEL_FROM_LIST_NOLOCK(list, obj); \ + } while (0); \ + locker.unlock(); \ + } while (0) typedef enum PoolAllocState { PAS_FIRST, PAS_EMPTY, PAS_NONE } PoolAllocStateT; @@ -192,7 +208,8 @@ typedef struct tagPoolStatsSt { uint64_t m_poolGrossSize; int16_t m_fragmentationPercent; uint32_t m_perPoolOverhead; - uint32_t m_perPoolWaist; + uint32_t m_perPoolWaste; + uint32_t m_usedChunkCount; } PoolStatsSt; #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -222,11 +239,6 @@ public: * @brief Copy constructor. * @param other The object. */ - ObjPoolPtr(ObjPoolPtr& other) - { - m_data.m_ptr = other.m_data.m_ptr; - } - ObjPoolPtr(const ObjPoolPtr& other) { m_data.m_ptr = other.m_data.m_ptr; @@ -272,7 +284,9 @@ public: ObjPoolPtr& operator=(const ObjPoolPtr& right) { - m_data.m_ptr = right.m_data.m_ptr; + if (this != &right) { + m_data.m_ptr = right.m_data.m_ptr; + } return *this; } @@ -301,6 +315,15 @@ private: } m_data; }; +struct objpoolkey_less_fn { + bool operator()(const ObjPool* t1, const ObjPool* t2) const + { + return ((uint64_t)t1 < (uint64_t)t2); + } +}; + +using ObjPoolAddrSet_t = std::set; + class ObjAllocInterface { public: friend class ObjPool; @@ -311,11 +334,19 @@ public: uint16_t m_oixOffset; MemBufferClass m_type; bool m_global; +#ifdef ENABLE_MEMORY_CHECK + uint16_t m_actualSize; +#endif static ObjAllocInterface* GetObjPool(uint16_t size, bool local, uint8_t align = 8); - static void FreeObjPool(ObjAllocInterface** pool); + static void FreeObjPool(ObjAllocInterface** pool) noexcept; - explicit ObjAllocInterface(bool isGlobal) : m_global(isGlobal) + explicit ObjAllocInterface(bool isGlobal) + : m_objList(nullptr), + m_size(0), + m_oixOffset(0), + m_type(MemBufferClass::MEM_BUFFER_CLASS_INVALID), + m_global(isGlobal) {} virtual ~ObjAllocInterface(); @@ -330,8 +361,9 @@ public: inline T* Alloc(Args&&... args) { void* buf = Alloc(); - if (unlikely(buf == nullptr)) + if (unlikely(buf == nullptr)) { return nullptr; + } return new (buf) T(std::forward(args)...); } @@ -346,11 +378,14 @@ public: } virtual void ClearThreadCache() = 0; + virtual void ClearAllThreadCache() = 0; virtual void ClearFreeCache() = 0; void GetStats(PoolStatsSt& stats); - void PrintStats(PoolStatsSt& stats, const char* prefix = "", LogLevel level = LogLevel::LL_DEBUG); + void GetStatsEx(PoolStatsSt& stats, ObjPoolAddrSet_t* poolSet); + void PrintStats(const PoolStatsSt& stats, const char* prefix = "", LogLevel level = LogLevel::LL_DEBUG) const; void Print(const char* prefix, LogLevel level = LogLevel::LL_DEBUG); + size_t CalcRequiredMemDiff(const PoolStatsSt& stats, size_t newSize, uint8_t align = 8) const; protected: static MemBufferClass CalcBufferClass(uint16_t size); @@ -374,6 +409,7 @@ public: ObjPool(uint16_t size, MemBufferClass type, ObjAllocInterface* app) { + m_listCounter = 0; m_parent = app; m_owner = -1; m_objNext = nullptr; @@ -382,39 +418,41 @@ public: *(uint32_t*)(&m_head.m_fill2[3]) = 0xDEADBEEF; m_overheadBytes = sizeof(ObjPool); uint8_t* ptr = m_head.m_data; - uint8_t* end = ptr + (1024 * MemBufferClassToSizeKb(type)) - sizeof(ObjPool); + uint8_t* end = ptr + (static_cast(KILO_BYTE) * MemBufferClassToSizeKb(type)) - sizeof(ObjPool); - m_freeCount = m_totalCount = (uint16_t)((end - m_head.m_data) / m_parent->m_size); + m_freeCount = m_totalCount = (uint16_t)((uint64_t)(end - m_head.m_data) / m_parent->m_size); uint8_t i = 0; for (; i < m_totalCount; i++) { + ptr[m_parent->m_oixOffset] = NOT_VALID; m_head.m_objIndexArr[i] = i; ptr += m_parent->m_size; - ptr[-1] = i; } for (; i < NUM_OBJS; i++) { m_head.m_objIndexArr[i] = NOT_VALID; } m_head.m_objIndexArr[i] = NOT_VALID; - m_head.m_nextFreeObj = -1; + m_head.m_nextFreeObj = static_cast(-1); m_head.m_nextOccupiedObj = m_totalCount - 1; m_overheadBytes += m_totalCount * OBJ_INDEX_SIZE; m_notUsedBytes = (int)(end - ptr); } - ~ObjPool() - {} + ~ObjPool(); inline void AllocNoLock(void** ret, PoolAllocStateT* state) { uint8_t ix = ++(m_head.m_nextFreeObj); uint8_t oix = m_head.m_objIndexArr[ix]; m_head.m_objIndexArr[ix] = NOT_VALID; - *ret = (m_head.m_data + oix * m_parent->m_size); + *ret = (m_head.m_data + static_cast(oix) * m_parent->m_size); ((uint8_t*)(*ret))[m_parent->m_oixOffset] = oix; --m_freeCount; if (m_freeCount == 0) { *state = PAS_EMPTY; } +#ifdef ENABLE_MEMORY_CHECK + AllocForMemCheck(ret); +#endif } inline void Alloc(void** ret, PoolAllocStateT* state) @@ -425,12 +463,15 @@ public: } uint8_t oix = m_head.m_objIndexArr[ix]; m_head.m_objIndexArr[ix] = NOT_VALID; - *ret = (m_head.m_data + oix * m_parent->m_size); + *ret = (m_head.m_data + static_cast(oix) * m_parent->m_size); ((uint8_t*)(*ret))[m_parent->m_oixOffset] = oix; uint16_t c = __sync_sub_and_fetch(&m_freeCount, 1); if (c == 0) { *state = PAS_EMPTY; } +#ifdef ENABLE_MEMORY_CHECK + AllocForMemCheck(ret); +#endif } inline void ReleaseNoLock(uint8_t oix, PoolAllocStateT* state) @@ -457,83 +498,88 @@ public: } } - static ObjPool* GetObjPool(uint16_t size, ObjAllocInterface* app, MemBufferClass type, bool global) - { -#ifdef TEST_STAT_ALLOC - uint64_t start_time = GetSysClock(); -#endif -#ifndef MEM_ACTIVE - void* p = (void*)malloc((1024 * MemBufferClassToSizeKb(type))); -#else - void* p; + static ObjPool* GetObjPool(uint16_t size, ObjAllocInterface* app, MemBufferClass type, bool global); - if (global == true) { - p = MemBufferAllocGlobal(type); - } else { -#ifdef MEM_SESSION_ACTIVE - uint32_t bufferSize = 1024 * MemBufferClassToSizeKb(type); - p = MemSessionAlloc(bufferSize); - if (p) { - DetailedMemoryStatisticsProvider::m_provider->AddLocalBuffersUsed(MOTCurrentNumaNodeId, type); - } -#else - p = MemBufferAllocLocal(type); -#endif // MEM_SESSION_ACTIVE - } -#endif // MEM_ACTIVE -#ifdef TEST_STAT_ALLOC - uint64_t end_time = GetSysClock(); - MemoryStatisticsProvider::m_provider->AddMallocTime( - CpuCyclesLevelTime::CyclesToNanoseconds(end_time - start_time)); + static void DelObjPool(void* ptr, MemBufferClass type, bool global); + +private: +#ifdef ENABLE_MEMORY_CHECK + void AllocForMemCheck(void** ret); #endif - ObjPool* o = nullptr; - if (p) { - o = new (p) ObjPool(size, type, app); - } else { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "N/A", - "Failed to allocate %s %s buffer for object pool", - MemBufferClassToString(type), - global ? "global" : "local"); + + DECLARE_CLASS_LOGGER(); +}; + +class ObjPoolItr { +public: + explicit ObjPoolItr(ObjPool* pool) : m_itr(0), m_oix(NOT_VALID), m_obj(nullptr), m_pool(pool) + { + if (m_pool->m_freeCount == m_pool->m_totalCount) { + m_itr = m_pool->m_totalCount; } - return o; } - - static void DelObjPool(void* ptr, MemBufferClass type, bool global) + ~ObjPoolItr() { -#ifdef TEST_STAT_ALLOC - uint64_t start_time = GetSysClock(); + m_pool = nullptr; + m_obj = nullptr; + } + uint8_t* Next() + { + m_obj = nullptr; +#ifdef ENABLE_MEMORY_CHECK + m_mallocPtr = nullptr; #endif -#ifndef MEM_ACTIVE - free(ptr); -#else - if (global == true) { - MemBufferFreeGlobal(ptr, type); - } else { -#ifdef MEM_SESSION_ACTIVE - MemSessionFree(ptr); - DetailedMemoryStatisticsProvider::m_provider->AddLocalBuffersFreed(MOTCurrentNumaNodeId, type); -#else - MemBufferFreeLocal(ptr, type); -#endif // MEM_SESSION_ACTIVE + m_oix = NOT_VALID; + while (m_itr < m_pool->m_totalCount) { + m_obj = m_pool->m_head.m_data + (static_cast(m_itr) * m_pool->m_parent->m_size); + m_oix = m_obj[m_pool->m_parent->m_oixOffset]; + if (m_oix == NOT_VALID) { + m_itr++; + m_obj = nullptr; + m_oix = NOT_VALID; + continue; + } + break; + } + m_itr++; +#ifdef ENABLE_MEMORY_CHECK + if (m_obj != nullptr) { + m_mallocPtr = (uint8_t*)(*(uint64_t*)m_obj) + MEMCHECK_METAINFO_SIZE; + } + return m_mallocPtr; +#else + return m_obj; +#endif + } + void ReleaseCurrent() + { + PoolAllocStateT state = PAS_NONE; + if (m_oix != NOT_VALID) { + m_pool->ReleaseNoLock(m_oix, &state); + } +#ifdef ENABLE_MEMORY_CHECK + if (m_mallocPtr != nullptr) { + free(m_mallocPtr - MEMCHECK_METAINFO_SIZE); + m_mallocPtr = nullptr; } -#endif // MEM_ACTIVE -#ifdef TEST_STAT_ALLOC - uint64_t end_time = GetSysClock(); - MemoryStatisticsProvider::m_provider->AddFreeTime( - CpuCyclesLevelTime::CyclesToNanoseconds(end_time - start_time)); #endif } private: - DECLARE_CLASS_LOGGER(); + uint16_t m_itr; + uint8_t m_oix; + uint8_t* m_obj; + ObjPool* m_pool; +#ifdef ENABLE_MEMORY_CHECK + uint8_t* m_mallocPtr; +#endif }; inline void ObjPoolPtr::operator++() { Get()->m_listCounter++; m_data.m_slice[LIST_PTR_SLICE_IX] = Get()->m_listCounter; - MEMORY_BARRIER; + COMPILER_BARRIER; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/memory/spsc_allocator.cpp b/src/gausskernel/storage/mot/core/memory/spsc_allocator.cpp new file mode 100644 index 000000000..6fa7555ed --- /dev/null +++ b/src/gausskernel/storage/mot/core/memory/spsc_allocator.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * spsc_allocator.cpp + * SPSC Variable size allocator implementation. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/memory/object_pool.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "spsc_allocator.h" +#include "sys_numa_api.h" + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(SPSCVarSizeAllocator, Memory) + +SPSCVarSizeAllocator* SPSCVarSizeAllocator::GetSPSCAllocator(uint32_t size) +{ + SPSCVarSizeAllocator* res = nullptr; + + if (size > SPSC_ALLOCATOR_MAXSIZE) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Allocate SPSCVarSizeAllocator", + "Requested size %u exceeds max allowed %u", + size, + SPSC_ALLOCATOR_MAXSIZE); + return res; + } + + void* buf = MotSysNumaAllocInterleaved(size); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Allocate SPSCVarSizeAllocator", "Failed to allocate memory of %u size", size); + } else { + res = new (buf) SPSCVarSizeAllocator(size); + } + + return res; +} + +void SPSCVarSizeAllocator::FreeSPSCAllocator(SPSCVarSizeAllocator* spsc) +{ + if (spsc != nullptr) { + MotSysNumaFree((void*)spsc, spsc->GetSize()); + } +} +} // namespace MOT \ No newline at end of file diff --git a/src/gausskernel/storage/mot/core/memory/spsc_allocator.h b/src/gausskernel/storage/mot/core/memory/spsc_allocator.h new file mode 100644 index 000000000..5c13336b8 --- /dev/null +++ b/src/gausskernel/storage/mot/core/memory/spsc_allocator.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * spsc_allocator.h + * SPSC Variable size allocator implementation. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/memory/spsc_allocator.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef SPSC_ALLOCATOR_H +#define SPSC_ALLOCATOR_H + +#include "global.h" +#include "utilities.h" +#include "mot_atomic_ops.h" +#include "mm_def.h" +#include "mot_error.h" + +namespace MOT { + +#define SPSC_ALLOCATOR_MAXSIZE (1 << 30) // 1GB +#define SPSC_ALIGN_SIZE 8 +#define PTR_ALLOC_MASK (uint32_t)0x80EADBEAF +#define PTR_FREE_MASK (uint32_t)0x7FEADBEAF +#define PTR_UNUSED_MASK (uint32_t)0x81EADBEAF + +typedef struct PACKED tagSPSCPtrSt { + uint32_t m_mask; + uint32_t m_size; + uint8_t m_data[0]; +} SPSCPtrSt; + +#define PTR_META_OFFSET (sizeof(SPSCPtrSt)) + +class PACKED SPSCVarSizeAllocator { +public: + static SPSCVarSizeAllocator* GetSPSCAllocator(uint32_t size); + static void FreeSPSCAllocator(SPSCVarSizeAllocator* spsc); + + inline uint32_t GetSize() const + { + return (m_size + sizeof(SPSCVarSizeAllocator)); + } + + explicit SPSCVarSizeAllocator(uint32_t size) + : m_head((SPSCPtrSt*)m_data), m_size(size - sizeof(SPSCVarSizeAllocator)) + { + m_head->m_mask = PTR_FREE_MASK; + m_tail = m_head; + m_end = (SPSCPtrSt*)(m_data + m_size); + for (int i = 0; i < 6; i++) { + m_filler[i] = 0xDEADBEEFDEADBEEF; + } + } + + ~SPSCVarSizeAllocator() + { + m_head = nullptr; + m_tail = nullptr; + m_end = nullptr; + } + + inline void* Alloc(uint32_t size) + { + void* res = nullptr; + uint32_t asize = ALIGN8(size + PTR_META_OFFSET); + SPSCPtrSt* newhead = (SPSCPtrSt*)(((uint8_t*)m_head) + asize); + SPSCPtrSt* currTail = m_tail; // read tail once + Prefetch(m_head); + if (m_head < currTail) { + if (newhead <= currTail) { + m_head->m_mask = PTR_ALLOC_MASK; + m_head->m_size = asize; + res = (void*)m_head->m_data; + m_head = newhead; + } + } else { + if (m_head == currTail && m_head->m_mask == PTR_ALLOC_MASK) { + return res; + } + if (newhead <= m_end) { + m_head->m_mask = PTR_ALLOC_MASK; + m_head->m_size = asize; + res = (void*)m_head->m_data; + if (newhead == m_end) { + m_head = (SPSCPtrSt*)m_data; + } else { + m_head = newhead; + } + } else { + newhead = (SPSCPtrSt*)(m_data + asize); + if (newhead <= currTail) { + m_head->m_mask = PTR_UNUSED_MASK; + m_head = (SPSCPtrSt*)m_data; + m_head->m_mask = PTR_ALLOC_MASK; + m_head->m_size = asize; + res = (void*)m_head->m_data; + m_head = newhead; + } + } + } + COMPILER_BARRIER; + return res; + } + + inline void Release(void* ptr) + { + if (ptr != nullptr) { + SPSCPtrSt* currHead = m_head; // load once + SPSCPtrSt* freePtr = (SPSCPtrSt*)(((uint8_t*)ptr) - PTR_META_OFFSET); + + if (freePtr < (SPSCPtrSt*)m_data || freePtr > m_end) { + MOT_LOG_PANIC("Pointer is not associated with current allocator: 0x%lx\n", (uintptr_t)ptr); + MOTAbort(ptr); + return; + } + if (freePtr->m_mask != PTR_ALLOC_MASK) { + MOT_LOG_PANIC("Detected double free of pointer or corruption: 0x%lx\n", (uintptr_t)ptr); + MOTAbort(ptr); + return; + } + freePtr->m_mask = PTR_FREE_MASK; + if (m_tail == freePtr) { + m_tail = (SPSCPtrSt*)(((uint8_t*)freePtr) + freePtr->m_size); + if (m_tail == m_end) { + m_tail = (SPSCPtrSt*)m_data; + } + } + + // check if we had un-ordered releases + while (true) { + if (m_tail->m_mask == PTR_ALLOC_MASK) { + break; + } else if (currHead == m_tail) { + break; + } else if (m_tail->m_mask == PTR_FREE_MASK) { + m_tail = (SPSCPtrSt*)(((uint8_t*)m_tail) + m_tail->m_size); + if (m_tail == m_end) { + m_tail = (SPSCPtrSt*)m_data; + } + } else if (m_tail->m_mask == PTR_UNUSED_MASK) { + m_tail = (SPSCPtrSt*)m_data; + } else { + break; + } + } + COMPILER_BARRIER; + } + } + +private: + DECLARE_CLASS_LOGGER(); + // DO NOT change the order of the members + SPSCPtrSt* m_head; + SPSCPtrSt* m_end; // should not be de-referenced, points beyond the data + uint64_t m_size; + uint64_t m_filler[6]; // m_head and m_tail should be CACHE_LINE_SIZE away from each other + SPSCPtrSt* m_tail; + // do not add members after + uint8_t m_data[0]; +}; +} // namespace MOT + +#endif /* SPSC_ALLOCATOR_H */ diff --git a/src/gausskernel/storage/mot/core/memory/sys_numa_api.cpp b/src/gausskernel/storage/mot/core/memory/sys_numa_api.cpp index dbd4bc4e5..a12fdf396 100644 --- a/src/gausskernel/storage/mot/core/memory/sys_numa_api.cpp +++ b/src/gausskernel/storage/mot/core/memory/sys_numa_api.cpp @@ -32,15 +32,15 @@ #include "postgres.h" #include "knl/knl_thread.h" -#include -#include +#include +#include #include #include #include -#include +#include #include -#include -#include +#include +#include #include #include #include @@ -52,19 +52,19 @@ #endif /* Flags for mbind */ -#define MPOL_MF_STRICT (1 << 0) /* Verify existing pages in the mapping */ -#define MPOL_MF_MOVE (1 << 1) /* Move pages owned by this process to conform to mapping */ -#define MPOL_MF_MOVE_ALL (1 << 2) /* Move every page to conform to mapping */ +#define MPOL_MF_STRICT (1U) /* Verify existing pages in the mapping */ +#define MPOL_MF_MOVE (1U << 1) /* Move pages owned by this process to conform to mapping */ +#define MPOL_MF_MOVE_ALL (1U << 2) /* Move every page to conform to mapping */ // some required utility macros -#define ROUND_UP(x, y) (((x) + (y)-1) & ~((y)-1)) +#define ROUND_UP(x, y) (((x) + (y) - 1) & ~((y) - 1)) #define CPU_BYTES(x) (ROUND_UP(x, sizeof(long))) #define CPU_LONGS(x) (CPU_BYTES(x) / sizeof(long)) -#define HOW_MANY(x, y) (((x) + ((y)-1)) / (y)) -#define BITS_PER_LONG (8 * sizeof(unsigned long)) -#define BITS_PER_INT (8 * sizeof(unsigned int)) -#define LONGS_PER_BITS(n) HOW_MANY(n, BITS_PER_LONG) +#define HOW_MANY(x, y) (((x) + ((y) - 1)) / (y)) +#define BITS_PER_ULONG ((unsigned int)8 * sizeof(unsigned long)) +#define BITS_PER_UINT ((unsigned int)8 * sizeof(unsigned int)) +#define LONGS_PER_BITS(n) HOW_MANY(n, BITS_PER_ULONG) #define BYTES_PER_BITS(x) ((x + 7) / 8) // define maximum number of NUMA nodes @@ -93,16 +93,21 @@ typedef struct PACKED BitMask_ST { unsigned long m_maskp[0]; } BitMaskSt; -#define BITMASK_NBYTES(bmp) (LONGS_PER_BITS(bmp->m_size) * sizeof(unsigned long)) -#define BITMASK_GETBIT(bmp, n) \ - (((unsigned int)n < bmp->m_size) ? ((bmp->m_maskp[n / BITS_PER_LONG] >> (n % BITS_PER_LONG)) & 1) : 0) -#define BITMASK_SETBIT(bmp, n) \ - if ((unsigned int)n < bmp->m_size) { \ - bmp->m_maskp[n / BITS_PER_LONG] |= 1UL << (n % BITS_PER_LONG); \ +#define BITMASK_NBYTES(bmp) (LONGS_PER_BITS((bmp)->m_size) * sizeof(unsigned long)) + +#define BITMASK_GETBIT(bmp, n) \ + (((unsigned int)(n) < (bmp)->m_size) \ + ? (((bmp)->m_maskp[(unsigned int)(n) / BITS_PER_ULONG] >> ((unsigned int)(n) % BITS_PER_ULONG)) & 1) \ + : 0) + +#define BITMASK_SETBIT(bmp, n) \ + if ((unsigned int)(n) < (bmp)->m_size) { \ + (bmp)->m_maskp[(unsigned int)(n) / BITS_PER_ULONG] |= 1UL << ((unsigned int)(n) % BITS_PER_ULONG); \ } -#define BITMASK_CLEARBIT(bmp, n) \ - if ((unsigned int)n < bmp->m_size) { \ - bmp->m_maskp[n / BITS_PER_LONG] &= ~(1UL << (n % BITS_PER_LONG)); \ + +#define BITMASK_CLEARBIT(bmp, n) \ + if ((unsigned int)(n) < (bmp)->m_size) { \ + (bmp)->m_maskp[(unsigned int)(n) / BITS_PER_ULONG] &= ~(1UL << ((unsigned int)(n) % BITS_PER_ULONG)); \ } #define BITMASK_FREE(x) \ @@ -111,11 +116,11 @@ typedef struct PACKED BitMask_ST { x = nullptr; \ } -#define BITMASK_ONSTACK(name, x) \ - int sz = sizeof(BitMaskSt) + (LONGS_PER_BITS(x) * sizeof(unsigned long)); \ - char _bmp_buf[sz]; \ - BitMaskSt* name = (BitMaskSt*)_bmp_buf; \ - bzero(name, sz); \ +#define BITMASK_ONSTACK(name, x) \ + size_t sz = sizeof(BitMaskSt) + (LONGS_PER_BITS(x) * sizeof(unsigned long)); \ + char _bmp_buf[sz]; \ + BitMaskSt* name = (BitMaskSt*)_bmp_buf; \ + bzero(name, sz); \ name->m_size = x; // Report errors/warnings @@ -145,13 +150,13 @@ extern void MotSysNumaDumpMMapError( int errnum, void* address, size_t length, int prot, int flags, int fd, off_t offset); // Static variables -static const char* MASK_SIZE_FILE = "/proc/self/status"; -static const char* NODE_MASK_PREFIX = "Mems_allowed:\t"; +static const char* const MASK_SIZE_FILE = "/proc/self/status"; +static const char* const NODE_MASK_PREFIX = "Mems_allowed:\t"; static const int NODE_MASK_PREFIX_SIZE = strlen(NODE_MASK_PREFIX); -static const char* NODE_CPU_MAP_FILE = "/sys/devices/system/node/node%d/cpumap"; +static const char* const NODE_CPU_MAP_FILE = "/sys/devices/system/node/node%d/cpumap"; #define MOTBindPolicy t_thrd.mot_cxt.bindPolicy #define MOTMBindFlags t_thrd.mot_cxt.mbindFlags -static int g_nodeMaskSize = 0; +static unsigned int g_nodeMaskSize = 0; static int g_cpuMaskSize = 0; static int g_maxConfNode = -1; static int g_maxConfCpu = -1; @@ -210,7 +215,9 @@ void* MotSysNumaAllocInterleaved(size_t size) g_allNodesBm->m_size + 1, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -237,7 +244,9 @@ void* MotSysNumaAllocOnNode(size_t size, int node) bmp->m_size + 1, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -256,7 +265,9 @@ void* MotSysNumaAllocLocal(size_t size) } else { if (syscall(__NR_mbind, (intptr_t)mem, size, MPOL_PREFERRED, nullptr, 0, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -277,19 +288,19 @@ static void* MotSysNumaMapAligned(size_t size, size_t align) if (((uint64_t)mem) % align != 0) { // take the slow route - munmap(mem, size); + (void)munmap(mem, size); mem = MOTSysNumaMmap(0, size + align, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); if (mem == MAP_FAILED) { return mem; } else { uint64_t offset = ((uint64_t)mem) % align; if (offset == 0) { // aligned this time so we only need to unmap suffix of align bytes - munmap(((char*)mem) + size, align); + (void)munmap(((char*)mem) + size, align); } else { size_t leadingUseless = align - offset; // guaranteed to be non-negative - munmap(mem, leadingUseless); + (void)munmap(mem, leadingUseless); void* alignedMem = ((char*)mem) + leadingUseless; - munmap(((char*)alignedMem) + size, offset); + (void)munmap(((char*)alignedMem) + size, offset); mem = alignedMem; } } @@ -317,7 +328,9 @@ void* MotSysNumaAllocAlignedInterleaved(size_t size, size_t align) g_allNodesBm->m_size + 1, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -345,7 +358,9 @@ void* MotSysNumaAllocAlignedOnNode(size_t size, size_t align, int node) bmp->m_size + 1, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -365,7 +380,9 @@ void* MotSysNumaAllocAlignedLocal(size_t size, size_t align) } else { if (syscall(__NR_mbind, (intptr_t)mem, size, MPOL_PREFERRED, nullptr, 0, MOTMBindFlags) != 0) { MotSysNumaReportError("mbind"); - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } mem = nullptr; } } @@ -374,7 +391,9 @@ void* MotSysNumaAllocAlignedLocal(size_t size, size_t align) void MotSysNumaFree(void* mem, size_t size) { - munmap(mem, size); + if (munmap(mem, size) != 0) { + MotSysNumaReportError("munmap"); + } } int MotSysNumaAvailable() @@ -428,10 +447,11 @@ void MotSysNumaSetBindPolicy(int strict) void MotSysNumaSetStrict(int flag) { - if (flag) + if (flag) { MOTMBindFlags |= MPOL_MF_STRICT; - else + } else { MOTMBindFlags &= ~MPOL_MF_STRICT; + } } void MotSysNumaSetPreferred(int node) @@ -490,7 +510,7 @@ static int MotSysNumaParseBitmap(char* line, BitMaskSt* mask) if (p > line && sizeof(unsigned long) == 8) { oldp--; - errno_t erc = memmove_s(p, oldp - p + 1, p + 1, oldp - p + 1); + errno_t erc = memmove_s(p, (oldp - p) + 1, p + 1, (oldp - p) + 1); securec_check(erc, "\0", "\0"); while (p > line && *p != ',') { --p; @@ -540,7 +560,7 @@ static void MotSysNumaSetConfiguredNodes() g_maxConfNode = node; } } - closedir(d); + (void)closedir(d); } } @@ -554,19 +574,23 @@ static void MotSysNumaSetTaskConstraints() g_allCpusBm = MotSysNumaBitmaskAlloc(g_cpuMaskSize); g_allNodesBm = MotSysNumaBitmaskAlloc(g_nodeMaskSize); - if ((f = fopen(MASK_SIZE_FILE, "r")) == nullptr) + if ((f = fopen(MASK_SIZE_FILE, "r")) == nullptr) { return; + } while (getline(&buffer, &buflen, f) > 0) { + MOT_ASSERT(buffer != nullptr); char* mask = strrchr(buffer, '\t') + 1; - if (strncmp(buffer, "Cpus_allowed:", 13) == 0) + if (strncmp(buffer, "Cpus_allowed:", 13) == 0) { g_numProcCpu = MotSysNumaReadBitmask(mask, g_allCpusBm); + } - if (strncmp(buffer, "Mems_allowed:", 13) == 0) + if (strncmp(buffer, "Mems_allowed:", 13) == 0) { g_numProcNode = MotSysNumaReadBitmask(mask, g_allNodesBm); + } } - fclose(f); + (void)fclose(f); if (buffer != nullptr) { free(buffer); } @@ -663,7 +687,7 @@ static unsigned int MotSysNumaBitmaskWeight(const BitMaskSt* bmp) static int MotSysNumaReadBitmask(char* s, BitMaskSt* bmp) { char* end = s; - unsigned tmplen = (bmp->m_size + BITS_PER_INT - 1) / BITS_PER_INT; + unsigned tmplen = (bmp->m_size + BITS_PER_UINT - 1) / BITS_PER_UINT; unsigned int tmp[tmplen]; unsigned int* start = tmp; unsigned int n = 0; @@ -696,7 +720,7 @@ static int MotSysNumaReadBitmask(char* s, BitMaskSt* bmp) while (n) { unsigned int w; unsigned long x = 0; - for (w = 0; n && w < BITS_PER_LONG; w += BITS_PER_INT) { + for (w = 0; n && w < BITS_PER_ULONG; w += BITS_PER_UINT) { x |= ((unsigned long)start[n-- - 1] << w); } @@ -708,7 +732,7 @@ static int MotSysNumaReadBitmask(char* s, BitMaskSt* bmp) static void MotSysNumaNodeCpuMaskCleanup(void) { if (g_nodeCpuBm != nullptr) { - for (int i = 0; i < g_nodeMaskSize; i++) { + for (unsigned int i = 0; i < g_nodeMaskSize; i++) { BITMASK_FREE(g_nodeCpuBm[i]); } free(g_nodeCpuBm); @@ -728,7 +752,7 @@ static void MotSysNumaNodeCpuMaskInit(void) g_nodeCpuBm = (BitMaskSt**)calloc(g_nodeMaskSize, sizeof(BitMaskSt*)); - for (int i = 0; i < g_nodeMaskSize; i++) { + for (unsigned int i = 0; i < g_nodeMaskSize; i++) { len = 0; FILE* f = nullptr; char* line = nullptr; @@ -762,7 +786,7 @@ static void MotSysNumaNodeCpuMaskInit(void) } if (f != nullptr) { - fclose(f); + (void)fclose(f); } } } @@ -782,7 +806,7 @@ static void MotSysNumaSetNodemaskSize() } if (line != nullptr) free(line); - fclose(fp); + (void)fclose(fp); } if (g_nodeMaskSize == 0) { diff --git a/src/gausskernel/storage/mot/core/memory/sys_numa_api.h b/src/gausskernel/storage/mot/core/memory/sys_numa_api.h index 893898730..a2f6b7f19 100644 --- a/src/gausskernel/storage/mot/core/memory/sys_numa_api.h +++ b/src/gausskernel/storage/mot/core/memory/sys_numa_api.h @@ -25,7 +25,7 @@ #ifndef SYS_NUMA_API_H #define SYS_NUMA_API_H -#include +#include // Memory policy mode constants (adapted from /usr/include/linux/mempolicy.h) #define MPOL_DEFAULT 0 diff --git a/src/gausskernel/storage/mot/core/mot.conf b/src/gausskernel/storage/mot/core/mot.conf index 1443c3ee7..02c4d100b 100644 --- a/src/gausskernel/storage/mot/core/mot.conf +++ b/src/gausskernel/storage/mot/core/mot.conf @@ -20,21 +20,20 @@ # GB = gigabytes # TB = terabytes # -# If no memory units are specified, then bytes are assumed. +# If no memory units are specified, then kilobytes are assumed. # # Some memory units can be also given in the form of percentage from max_process_memory configured # in postgresql.conf as follows: # # 20% # -# Time units: us = microseconds (can also specify micros or microseconds) -# ms = milliseconds (can also specify millis or milliseconds) +# Time units: ms = milliseconds (can also specify millis or milliseconds) # s = seconds (can also specify secs or seconds) # min = minutes (can also specify mins or minutes) # h = hours (can also specify hours) # d = days (can also specify days) # -# If no time units are specified, then microseconds are assumed. +# If no time units are specified, then milliseconds are assumed. #------------------------------------------------------------------------------ # REDO LOG @@ -93,10 +92,21 @@ # RECOVERY #------------------------------------------------------------------------------ -# Specifies the number of worker to use during checkpoint data recovery. +# Specifies the number of workers to use during checkpoint data recovery. # #checkpoint_recovery_workers = 3 +# Specifies the number of workers to use during redo recovery/replay. +# +#parallel_recovery_workers = 5 + +# Specifies the size of the queue used during recovery to hold redo log segments. +# This parameter also limits the maximum number of transactions that can be active (in progress) +# during parallel recovery. If this limit is reached, redo replay will wait for some of the +# transactions to commit before processing the redo log for new transactions. +# +#parallel_recovery_queue_size = 512 + #------------------------------------------------------------------------------ # STATISTICS #------------------------------------------------------------------------------ @@ -175,16 +185,16 @@ # When a thread-pool is used this value is ignored for user sessions (since their affinity is # governed by the thread-pool), but still used for internal MOT tasks. # Valid values are: fill-socket-first, equal-per-socket, fill-physical-first, none. -# Fill-socket-first attaches threads to cores in the same socket until the socket is full and then +# fill-socket-first attaches threads to cores in the same socket until the socket is full and then # moves on to the next socket. -# Equal-per-socket spreads threads evenly among all sockets. -# Fill-physical-first attaches threads to physical cores in the same socket until all physical +# equal-per-socket spreads threads evenly among all sockets. +# fill-physical-first attaches threads to physical cores in the same socket until all physical # cores are employed and then moves to the next socket. When all physical cores are used, then the # process begins again with hyper-threaded cores. # None disables any affinity configuration and lets the system scheduler decide on which core each # thread is scheduled to run. # -#affinity_mode = fill-physical-first +#affinity_mode = equal-per-socket # Configures the chunk directory mode (used for memory chunk lookup). # Lazy mode configures the chunk directory to load parts of it on demand and therefore reduces the @@ -335,10 +345,6 @@ # GARBAGE COLLECTION #------------------------------------------------------------------------------ -# Specifies whether to use the garbage collector. -# -#enable_gc = true - # Configures the memory threshold for the garbage collector. # Each session manages its own list of to-be-reclaimed objects, and performs its own garbage # collection during transaction commit. This value determines the total memory sum threshold of @@ -374,25 +380,38 @@ # JIT #------------------------------------------------------------------------------ +# Specifies whether to use JIT compilation and execution. +# JIT execution allows preparing Just-In-Time compiled code for prepared queries during their +# planning phase and for stored procedures during their compilation phase. The resulting JIT- +# compiled function is executed whenever the prepared query or the stored procedure is invoked. JIT +# compilation takes place in the form of LLVM. +# +#enable_mot_codegen = false + # Specifies whether to use JIT query compilation and execution for planned queries. # JIT query execution allows preparing Just-In-Time compiled code for a prepared query during its # planning phase. The resulting JIT-compiled function is executed whenever the prepared query is -# invoked. JIT compilation usually takes place in the form of LLVM. On platforms where LLVM is not -# natively supported, MOT provides a software-based fallback called TVM (Tiny Virtual Machine). +# invoked. JIT compilation takes place in the form of LLVM. # -#enable_mot_codegen = true +#enable_mot_query_codegen = true -# Specifies whether to use TVM (pseudo-LLVM) even though LLVM is supported on current platform. -# On platforms where LLVM is not natively supported, MOT automatically defaults to TVM, but on -# other LLVM is used by default. On these platforms, where LLVM is natively supported, this -# configuration item allows still using TVM for JIT compilation and execution. +# Specifies whether to use JIT query compilation and execution for stored procedures. +# JIT query execution allows preparing Just-In-Time compiled code for a stored procedure during its +# compilation phase. The resulting JIT-compiled function is executed whenever the stored procedure +# is invoked. # -#force_mot_pseudo_codegen = false +#enable_mot_sp_codegen = true -# Specifies whether to print emitted LLVM/TVM IR code for JIT-compiled queries. +# Specifies whether to print emitted LLVM IR code for JIT-compiled queries. # #enable_mot_codegen_print = false # Limits the amount of JIT queries allowed per user session. # -#mot_codegen_limit = 100 +#mot_codegen_limit = 50000 + +# Specifies whether to use JIT profiling. +# When using this option, the mot_jit_profile() function can be used to obtain run-time profile +# data for jitted stored procedures and queries. +# +#enable_mot_codegen_profile = true diff --git a/src/gausskernel/storage/mot/core/storage/CMakeLists.txt b/src/gausskernel/storage/mot/core/storage/CMakeLists.txt index 7463876f9..3f33c8ab9 100644 --- a/src/gausskernel/storage/mot/core/storage/CMakeLists.txt +++ b/src/gausskernel/storage/mot/core/storage/CMakeLists.txt @@ -12,7 +12,9 @@ add_static_objtarget(gausskernel_storage_mot_core_storage TGT_mot_core_storage_S set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/index + ${CMAKE_CURRENT_SOURCE_DIR}/sentinel ) add_subdirectory(index) +add_subdirectory(sentinel) diff --git a/src/gausskernel/storage/mot/core/storage/catalog_column_types.h b/src/gausskernel/storage/mot/core/storage/catalog_column_types.h index ab8837d21..8eea2d9bd 100644 --- a/src/gausskernel/storage/mot/core/storage/catalog_column_types.h +++ b/src/gausskernel/storage/mot/core/storage/catalog_column_types.h @@ -25,7 +25,7 @@ #ifndef _CATALOG_COLUMN_TYPES_H_ #define _CATALOG_COLUMN_TYPES_H_ -#include +#include namespace MOT { #define GetBytes1(x) (((uintptr_t)(x)) & 0x000000ff) diff --git a/src/gausskernel/storage/mot/core/storage/column.cpp b/src/gausskernel/storage/mot/core/storage/column.cpp index c22830e8c..bee1541c4 100644 --- a/src/gausskernel/storage/mot/core/storage/column.cpp +++ b/src/gausskernel/storage/mot/core/storage/column.cpp @@ -22,8 +22,8 @@ * ------------------------------------------------------------------------- */ -#include -#include +#include +#include #include #include "global.h" #include "column.h" @@ -36,6 +36,8 @@ extern uint16_t MOTDateToStr(uintptr_t src, char* destBuf, size_t len); namespace MOT { DECLARE_LOGGER(Column, Storage) +static const char* const MOT_DROPPED_COL_NAME = ".....dropped....."; +static const size_t MOT_DROPPED_COL_NAME_LEN = strlen(MOT_DROPPED_COL_NAME); // Class column const char* Column::ColumnTypeToStr(MOT_CATALOG_FIELD_TYPES type) @@ -120,18 +122,116 @@ Column::Column() { this->m_id = 0; this->m_size = 0; + this->m_keySize = 0; this->m_offset = 0; this->m_isNotNull = true; this->m_nameLen = 0; - errno_t erc = memset_s(&(this->m_name), sizeof(this->m_name), 0, sizeof(this->m_name)); + this->m_isDropped = false; + this->m_hasDefault = false; + this->m_defValue = 0; + this->m_defSize = 0; + this->m_isCommitted = true; + errno_t erc = memset_s(this->m_name, sizeof(this->m_name), 0, sizeof(this->m_name)); securec_check(erc, "\0", "\0"); erc = memset_s(&(this->m_type), sizeof(this->m_type), 0, sizeof(this->m_type)); securec_check(erc, "\0", "\0"); this->m_envelopeType = 0; } +RC Column::Clone(Column* col) +{ + m_id = col->m_id; + m_size = col->m_size; + m_keySize = col->m_keySize; + m_offset = col->m_offset; + m_isNotNull = col->m_isNotNull; + m_nameLen = col->m_nameLen; + m_numIndexesUsage = col->m_numIndexesUsage; + m_isDropped = col->m_isDropped; + m_hasDefault = col->m_hasDefault; + m_isCommitted = col->m_isCommitted; + m_envelopeType = col->m_envelopeType; + m_type = col->m_type; + errno_t erc = memcpy_s(m_name, sizeof(m_name), col->m_name, col->m_nameLen + 1); + securec_check(erc, "\0", "\0"); + if (m_hasDefault) { + RC rc = SetDefaultValue(col->m_defValue, col->m_defSize); + if (rc != RC_OK) { + return rc; + } + } + return RC_OK; +} + Column::~Column() -{} +{ + ResetDefaultValue(); +} + +RC Column::SetDefaultValue(uintptr_t val, size_t size) +{ + switch (m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: { + if (size > 0) { + m_defValue = (uintptr_t)malloc(size); + if (m_defValue == 0) { + return RC_MEMORY_ALLOCATION_ERROR; + } + errno_t erc = memcpy_s((void*)m_defValue, size, (void*)val, size); + securec_check(erc, "\0", "\0"); + } + m_defSize = size; + break; + } + default: + m_defSize = size; + m_defValue = val; + break; + } + m_hasDefault = true; + return RC_OK; +} + +void Column::ResetDefaultValue() +{ + if (m_hasDefault) { + switch (m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: { + if (m_defValue != 0) { + free((void*)m_defValue); + } + break; + } + default: + break; + } + m_defSize = 0; + m_defValue = 0; + m_hasDefault = false; + } +} + +void Column::SetDropped() +{ + m_isDropped = true; + m_offset = 0; + m_size = 0; + errno_t erc = memset_s(this->m_name, sizeof(this->m_name), 0, sizeof(this->m_name)); + securec_check(erc, "\0", "\0"); + erc = memcpy_s(&m_name[0], Column::MAX_COLUMN_NAME_LEN, MOT_DROPPED_COL_NAME, MOT_DROPPED_COL_NAME_LEN + 1); + securec_check(erc, "\0", "\0"); + ResetDefaultValue(); +} bool ColumnCHAR::Pack(uint8_t* dest, uintptr_t src, size_t len) { @@ -157,10 +257,14 @@ void ColumnCHAR::SetKeySize() m_keySize = m_size; } -uint16_t ColumnCHAR::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnCHAR::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 1) { - *destBuf = GetBytes1(*(uint8_t*)(data + m_offset)); + if (useDefault) { + *destBuf = (uint8_t)GetBytes1(m_defValue); + } else { + *destBuf = GetBytes1(*(uint8_t*)(data + m_offset)); + } return 1; } else return 0; @@ -194,10 +298,15 @@ void ColumnTINY::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnTINY::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTINY::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { - uint8_t val = GetBytes1(*(uint8_t*)(data + m_offset)); + uint8_t val; + if (useDefault) { + val = (uint8_t)GetBytes1(m_defValue); + } else { + val = GetBytes1(*(uint8_t*)(data + m_offset)); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%d", val); securec_check_ss(erc, "\0", "\0"); return erc; @@ -234,10 +343,15 @@ void ColumnSHORT::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnSHORT::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnSHORT::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 5) { - uint16_t val = GetBytes2(*(uint16_t*)(data + m_offset)); + uint16_t val; + if (useDefault) { + val = (uint16_t)GetBytes2(m_defValue); + } else { + val = GetBytes2(*(uint16_t*)(data + m_offset)); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%d", val); securec_check_ss(erc, "\0", "\0"); return erc; @@ -275,10 +389,15 @@ void ColumnINT::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnINT::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnINT::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 10) { - uint32_t val = GetBytes4(*(uint32_t*)(data + m_offset)); + uint32_t val; + if (useDefault) { + val = (uint32_t)GetBytes4(m_defValue); + } else { + val = GetBytes4(*(uint32_t*)(data + m_offset)); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%d", val); securec_check_ss(erc, "\0", "\0"); return erc; @@ -316,10 +435,15 @@ void ColumnLONG::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnLONG::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnLONG::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 20) { - uint64_t val = GetBytes8(*(uint64_t*)(data + m_offset)); + uint64_t val; + if (useDefault) { + val = (uint64_t)GetBytes8(m_defValue); + } else { + val = GetBytes8(*(uint64_t*)(data + m_offset)); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%ld", val); securec_check_ss(erc, "\0", "\0"); return erc; @@ -378,11 +502,15 @@ void ColumnFLOAT::SetKeySize() m_keySize = m_size + 2; } -uint16_t ColumnFLOAT::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnFLOAT::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 40) { FloatConvT t; - t.m_v = *(float*)(data + m_offset); + if (useDefault) { + t.m_v = (float)(m_defValue); + } else { + t.m_v = *(float*)(data + m_offset); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%.9g", t.m_v); securec_check_ss(erc, "\0", "\0"); return erc; @@ -450,11 +578,15 @@ void ColumnDOUBLE::SetKeySize() m_keySize = m_size + 3; } -uint16_t ColumnDOUBLE::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnDOUBLE::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 40) { DoubleConvT t; - t.m_v = *(double*)(data + m_offset); + if (useDefault) { + t.m_v = (double)(m_defValue); + } else { + t.m_v = *(double*)(data + m_offset); + } errno_t erc = snprintf_s(destBuf, len, len - 1, "%.17g", t.m_v); securec_check_ss(erc, "\0", "\0"); return erc; @@ -486,10 +618,14 @@ void ColumnDATE::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnDATE::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnDATE::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= MOT_MAXDATELEN) { - return MOTDateToStr(GetBytes4(*(uint32_t*)(data + m_offset)), destBuf, len); + if (useDefault) { + return MOTDateToStr(GetBytes4(m_defValue), destBuf, len); + } else { + return MOTDateToStr(GetBytes4(*(uint32_t*)(data + m_offset)), destBuf, len); + } } return 0; } @@ -518,7 +654,7 @@ void ColumnTIME::SetKeySize() m_keySize = m_size; } -uint16_t ColumnTIME::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTIME::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -552,7 +688,7 @@ void ColumnINTERVAL::SetKeySize() m_keySize = m_size; } -uint16_t ColumnINTERVAL::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnINTERVAL::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -586,7 +722,7 @@ void ColumnTINTERVAL::SetKeySize() m_keySize = m_size; } -uint16_t ColumnTINTERVAL::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTINTERVAL::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -620,7 +756,7 @@ void ColumnTIMETZ::SetKeySize() m_keySize = m_size; } -uint16_t ColumnTIMETZ::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTIMETZ::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -659,10 +795,14 @@ void ColumnTIMESTAMP::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnTIMESTAMP::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTIMESTAMP::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= MOT_MAXDATELEN) { - return MOTTimestampToStr(GetBytes8(*(uint64_t*)(data + m_offset)), destBuf, len); + if (useDefault) { + return MOTTimestampToStr(GetBytes8(m_defValue), destBuf, len); + } else { + return MOTTimestampToStr(GetBytes8(*(uint64_t*)(data + m_offset)), destBuf, len); + } } return 0; } @@ -696,10 +836,14 @@ void ColumnTIMESTAMPTZ::SetKeySize() m_keySize = m_size + 1; } -uint16_t ColumnTIMESTAMPTZ::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnTIMESTAMPTZ::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= MOT_MAXDATELEN) { - return MOTTimestampTzToStr(GetBytes8(*(uint64_t*)(data + m_offset)), destBuf, len); + if (useDefault) { + return MOTTimestampTzToStr(GetBytes8(m_defValue), destBuf, len); + } else { + return MOTTimestampTzToStr(GetBytes8(*(uint64_t*)(data + m_offset)), destBuf, len); + } } return 0; } @@ -737,7 +881,7 @@ void ColumnDECIMAL::SetKeySize() m_keySize = m_size; } -uint16_t ColumnDECIMAL::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnDECIMAL::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -749,9 +893,9 @@ uint16_t ColumnDECIMAL::PrintValue(uint8_t* data, char* destBuf, size_t len) bool ColumnVARCHAR::Pack(uint8_t* dest, uintptr_t src, size_t len) { - if (len > m_size) + if (len > (m_size - 4)) { return false; - + } *((uint32_t*)(dest + m_offset)) = len; errno_t erc = memcpy_s(dest + m_offset + 4, m_size - 4, (void*)src, len); securec_check(erc, "\0", "\0"); @@ -760,14 +904,16 @@ bool ColumnVARCHAR::Pack(uint8_t* dest, uintptr_t src, size_t len) bool ColumnVARCHAR::PackKey(uint8_t* dest, uintptr_t src, size_t len, uint8_t fill) { - *((uint32_t*)dest) = 0; - errno_t erc = memcpy_s(dest + 4, m_keySize - 4, (void*)src, len); + errno_t erc = memcpy_s(dest, m_keySize, (void*)src, len); securec_check(erc, "\0", "\0"); - if (fill != 0x00) { - erc = memset_s(dest + 4 + len, m_keySize - len - 4, fill, m_keySize - len - 4); - securec_check(erc, "\0", "\0"); + if (m_keySize > len) { + if (fill != 0x00) { + erc = memset_s(dest + len, m_keySize - len, fill, (m_keySize - len) - 1); + securec_check(erc, "\0", "\0"); + } + // zero padding for delimiter + dest[m_keySize - 1] = 0; } - return true; } @@ -779,15 +925,23 @@ void ColumnVARCHAR::Unpack(uint8_t* data, uintptr_t* dest, size_t& len) void ColumnVARCHAR::SetKeySize() { - m_keySize = m_size; + // zero padding of one byte + m_keySize = (m_size - sizeof(uint32_t)) + 1; } -uint16_t ColumnVARCHAR::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnVARCHAR::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { - uint32_t val_len = *(uint32_t*)(data + m_offset); + uint32_t val_len; + uintptr_t val; + if (useDefault) { + val_len = m_defSize; + val = m_defValue; + } else { + val_len = *(uint32_t*)(data + m_offset); + val = GetBytes8(data + m_offset + 4); + } if (len >= val_len) { - errno_t erc = - snprintf_s(destBuf, len, len - 1, "%*.*s", val_len, val_len, (char*)GetBytes8(data + m_offset + 4)); + errno_t erc = snprintf_s(destBuf, len, len - 1, "%*.*s", val_len, val_len, (char*)val); securec_check_ss(erc, "\0", "\0"); return erc; } @@ -796,9 +950,9 @@ uint16_t ColumnVARCHAR::PrintValue(uint8_t* data, char* destBuf, size_t len) bool ColumnBLOB::Pack(uint8_t* dest, uintptr_t src, size_t len) { - if (len > m_size) + if (len > (m_size - 4)) { return false; - + } *((uint32_t*)(dest + m_offset)) = len; errno_t erc = memcpy_s(dest + m_offset + 4, m_size - 4, (void*)src, len); securec_check(erc, "\0", "\0"); @@ -824,7 +978,7 @@ void ColumnBLOB::SetKeySize() m_keySize = m_size; } -uint16_t ColumnBLOB::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnBLOB::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -859,7 +1013,7 @@ void ColumnNULLBYTES::SetKeySize() m_keySize = m_size; } -uint16_t ColumnNULLBYTES::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnNULLBYTES::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -891,7 +1045,7 @@ void ColumnUNKNOWN::SetKeySize() MOT_ASSERT(false); } -uint16_t ColumnUNKNOWN::PrintValue(uint8_t* data, char* destBuf, size_t len) +uint16_t ColumnUNKNOWN::PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); diff --git a/src/gausskernel/storage/mot/core/storage/column.h b/src/gausskernel/storage/mot/core/storage/column.h index 2499f55b9..65e81fdbf 100644 --- a/src/gausskernel/storage/mot/core/storage/column.h +++ b/src/gausskernel/storage/mot/core/storage/column.h @@ -95,7 +95,7 @@ public: return m_keySize; } - virtual uint16_t PrintValue(uint8_t* data, char* destBuf, size_t len) + virtual uint16_t PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault = false) { if (len >= 3) { errno_t erc = snprintf_s(destBuf, len, len - 1, "NaN"); @@ -121,11 +121,39 @@ public: m_numIndexesUsage--; } - const char* GetTypeStr() + const char* GetTypeStr() const { return ColumnTypeToStr(m_type); } + RC SetDefaultValue(uintptr_t val, size_t size); + + void ResetDefaultValue(); + + void SetDropped(); + + inline void SetIsDropped(bool isDropped) + { + m_isDropped = isDropped; + } + + inline bool GetIsDropped() const + { + return m_isDropped; + } + + inline void SetIsCommitted(bool isCommited) + { + m_isCommitted = isCommited; + } + + inline bool GetIsCommitted() const + { + return m_isCommitted; + } + + RC Clone(Column* col); + // class non-copy-able, non-assignable, non-movable /** @cond EXCLUDE_DOC */ Column(const Column&) = delete; @@ -149,10 +177,10 @@ public: uint64_t m_offset = 0; /** @var Column name. */ - char m_name[MAX_COLUMN_NAME_LEN]; + char m_name[MAX_COLUMN_NAME_LEN] = {0}; /** @var Column name length */ - uint16_t m_nameLen; + uint16_t m_nameLen = 0; /** @var Number of indexes which using this column as part of their key. */ uint16_t m_numIndexesUsage = 0; @@ -161,22 +189,38 @@ public: MOT_CATALOG_FIELD_TYPES m_type; /** @var Column does not allow null values. */ - bool m_isNotNull; + bool m_isNotNull = false; /** @var Envelope column type. */ - unsigned int m_envelopeType; + unsigned int m_envelopeType = 0; + + /** @var Column is dropped. */ + bool m_isDropped = 0; + + /** @var Column has default value. */ + bool m_hasDefault = false; + + /** @var Column's default value. */ + uintptr_t m_defValue = 0; + + /** @var Column's default value size. */ + size_t m_defSize = 0; + + /** @var Column's commit state. */ + bool m_isCommitted = false; }; // derived column classes -#define X(Enum, String) \ - class alignas(CACHE_LINE_SIZE) Column##Enum : public Column { \ - virtual ~Column##Enum() \ - {} \ - virtual bool Pack(uint8_t* dest, uintptr_t src, size_t len); \ - virtual bool PackKey(uint8_t* dest, uintptr_t src, size_t len, uint8_t fill = 0x00); \ - virtual void Unpack(uint8_t* data, uintptr_t* dest, size_t& len); \ - virtual void SetKeySize(); \ - virtual uint16_t PrintValue(uint8_t* data, char* destBuf, size_t len); \ +#define X(Enum, String) \ + class alignas(CACHE_LINE_SIZE) Column##Enum : public Column { \ + public: \ + ~Column##Enum() override \ + {} \ + bool Pack(uint8_t* dest, uintptr_t src, size_t len) override; \ + bool PackKey(uint8_t* dest, uintptr_t src, size_t len, uint8_t fill) override; \ + void Unpack(uint8_t* data, uintptr_t* dest, size_t& len) override; \ + void SetKeySize() override; \ + uint16_t PrintValue(uint8_t* data, char* destBuf, size_t len, bool useDefault) override; \ }; TYPENAMES #undef X diff --git a/src/gausskernel/storage/mot/core/storage/index/index.cpp b/src/gausskernel/storage/mot/core/storage/index/index.cpp index 7967cc510..e892713ac 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/index.cpp @@ -23,6 +23,7 @@ */ #include "index.h" +#include "index_defs.h" #include "row.h" #include "sentinel.h" #include "object_pool_compact.h" @@ -135,7 +136,8 @@ void Index::BuildKey(Table* table, const Row* row, Key* key) securec_check(erc, "\0", "\0"); // Need to verify we copy secondary index keys if (IsFakePrimary()) { - key->CpKey(const_cast(row->GetSurrogateKeyBuff()), m_keyLength); + uint64_t surrogateprimaryKey = row->GetSurrogateKey(); + key->CpKey((uint8_t*)(&surrogateprimaryKey), m_keyLength); } else if (row->IsInternalKey()) { buf = (const_cast(row))->GetInternalKeyBuff(GetIndexOrder()); key->CpKey(buf, m_keyLength); @@ -143,12 +145,19 @@ void Index::BuildKey(Table* table, const Row* row, Key* key) for (int i = 0; i < m_numKeyFields; i++) { Column* col = table->GetField(m_columnKeyFields[i]); - if (BITMAP_GET(data, (col->m_id - 1))) { + if (!col->GetIsCommitted() && row->GetTable() != table) { + if (col->m_hasDefault) { + (void)col->PackKey(buf + offset, col->m_defValue, col->m_defSize); + } else { + erc = memset_s(buf + offset, m_keyLength - offset, 0x00, m_lengthKeyFields[i]); + securec_check(erc, "\0", "\0"); + } + } else if (BITMAP_GET(data, (col->m_id - 1))) { uintptr_t val = 0; size_t len = 0; col->Unpack(data, &val, len); - col->PackKey(buf + offset, val, len); + (void)col->PackKey(buf + offset, val, len); } else { MOT_ASSERT((offset + m_lengthKeyFields[i]) <= m_keyLength); // NOTE: we should consider different data types and fill NULL value according to a data type @@ -161,7 +170,7 @@ void Index::BuildKey(Table* table, const Row* row, Key* key) if (!m_unique) { uint64_t rowId = row->GetRowId(); - const_cast(key)->FillValue(reinterpret_cast(&rowId), + (void)const_cast(key)->FillValue(reinterpret_cast(&rowId), NON_UNIQUE_INDEX_SUFFIX_LEN, m_keyLength - NON_UNIQUE_INDEX_SUFFIX_LEN); } @@ -179,7 +188,7 @@ void Index::BuildErrorMsg(Table* table, const Row* row, char* destBuf, size_t le destBuf[offset++] = '('; for (int i = 0; i < m_numKeyFields; i++) { - Column* col = table->GetField(m_columnKeyFields[i]); + Column* col = m_table->GetField(m_columnKeyFields[i]); erc = memcpy_s(destBuf + offset, len - offset, col->m_name, col->m_nameLen); securec_check(erc, "\0", "\0"); offset += col->m_nameLen; @@ -192,9 +201,17 @@ void Index::BuildErrorMsg(Table* table, const Row* row, char* destBuf, size_t le destBuf[offset++] = '('; for (int i = 0; i < m_numKeyFields; i++) { - Column* col = table->GetField(m_columnKeyFields[i]); + Column* col = m_table->GetField(m_columnKeyFields[i]); - if (BITMAP_GET(data, (col->m_id - 1))) { + if (!col->GetIsCommitted() && row->GetTable() != table) { + if (col->m_hasDefault) { + offset += col->PrintValue(data, destBuf + offset, len - offset, true); + } else { + erc = snprintf_s(destBuf, len - offset, 4, "NULL"); + securec_check_ss(erc, "\0", "\0"); + offset += erc; + } + } else if (BITMAP_GET(data, (col->m_id - 1))) { offset += col->PrintValue(data, destBuf + offset, len - offset); } else { erc = snprintf_s(destBuf, len - offset, 4, "NULL"); @@ -208,12 +225,16 @@ void Index::BuildErrorMsg(Table* table, const Row* row, char* destBuf, size_t le destBuf[offset] = 0; } -void Index::Truncate(bool isDrop) +RC Index::Truncate(bool isDrop) { ObjAllocInterface::FreeObjPool(&m_keyPool); ObjAllocInterface::FreeObjPool(&m_sentinelPool); + ObjAllocInterface::FreeObjPool(&m_sSentinelVersionPool); - this->ReInitIndex(); + RC res = this->ReInitIndex(isDrop); + if (res != RC_OK) { + return res; + } if (!isDrop) { m_keyPool = ObjAllocInterface::GetObjPool(sizeof(Key) + ALIGN8(m_keyLength), false); @@ -222,21 +243,39 @@ void Index::Truncate(bool isDrop) "Truncate Index", "Failed to allocate key pool for index %s after truncation", m_name.c_str()); + return RC_MEMORY_ALLOCATION_ERROR; + } + if (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY) { + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinel), false); + } else { + if (m_unique) { + m_sSentinelVersionPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinelNode), false); + if (m_sSentinelVersionPool == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Truncate Index", + "Failed to allocate sentinel objects pool for index %s after truncation", + m_name.c_str()); + } + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinelUnique), false); + } else { + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinel), false); + } } - m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(Sentinel), false); if (m_sentinelPool == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Truncate Index", "Failed to allocate sentinel pool for index %s after truncation", m_name.c_str()); + return RC_MEMORY_ALLOCATION_ERROR; } } + return RC_OK; } -bool Index::IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, RC& rc) +bool Index::IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, RC& rc, bool isRecovery) { bool inserted = false; - Sentinel* sentinel = m_sentinelPool->Alloc(); + Sentinel* sentinel = SentinelAlloc(); if (unlikely(sentinel == nullptr)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Index Insert", @@ -248,37 +287,50 @@ bool Index::IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, sentinel->Init(this, nullptr); MOT_ASSERT(sentinel->GetCounter() == 1); - if (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY) { - sentinel->SetPrimaryIndex(); - } + sentinel->SetIndexOrder(m_indexOrder); bool retryInsert = true; - while (retryInsert) { outputSentinel = IndexInsertImpl(key, sentinel, inserted, pid); // sync between rollback/delete and insert - if (inserted == false) { + if (!inserted) { if (unlikely(outputSentinel == nullptr)) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "Index Insert", "Failed to insert sentinel to index %s", m_name.c_str()); rc = RC_MEMORY_ALLOCATION_ERROR; - m_sentinelPool->Release(sentinel); - sentinel = nullptr; + SentinelRelease(sentinel); return false; } + if (outputSentinel->IsCounterReachedSoftLimit() and isRecovery == false) { + if (GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + Row* row = outputSentinel->GetData(); + if (row) { + if (row->IsRowDeleted() == false) { + rc = RC_UNIQUE_VIOLATION; + retryInsert = false; + continue; + } + } + } else { + rc = RC_UNIQUE_VIOLATION; + retryInsert = false; + continue; + } + } // Spin if the counter is 0 - aborting in parallel or sentinel is marks for commit - if (outputSentinel->RefCountUpdate(INC, pid) == RC_OK) + if (outputSentinel->RefCountUpdate(INC) == RC_OK) { retryInsert = false; + } } else { retryInsert = false; } } - if (inserted == false) { + if (!inserted) { MOT_ASSERT(outputSentinel != sentinel); // Failed sentinels return to the pool - m_sentinelPool->Release(sentinel); + SentinelRelease(sentinel); return false; } else { // I am the owner of the inserted sentinel counter = 1 @@ -292,8 +344,9 @@ bool Index::IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, Sentinel* Index::IndexInsert(const Key* key, Row* row, uint32_t pid) { bool inserted = false; + PrimarySentinelNode* node = nullptr; Sentinel* currSentinel = nullptr; - Sentinel* sentinel = m_sentinelPool->Alloc(); + Sentinel* sentinel = SentinelAlloc(); if (unlikely(sentinel == nullptr)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Index Insert", @@ -301,31 +354,46 @@ Sentinel* Index::IndexInsert(const Key* key, Row* row, uint32_t pid) m_name.c_str()); return nullptr; } - + if (GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + node = SentinelNodeAlloc(); + if (node == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Sentinel node", "Failed to create new sentinel entry"); + SentinelRelease(sentinel); + return nullptr; + } + } sentinel->Init(this, nullptr); sentinel->UnSetDirty(); currSentinel = IndexInsertImpl(key, sentinel, inserted, pid); if (currSentinel != nullptr) { // no need to report to full error stack SetLastError(MOT_ERROR_UNIQUE_VIOLATION, MOT_SEVERITY_NORMAL); - m_sentinelPool->Release(sentinel); - sentinel = nullptr; + SentinelRelease(sentinel); + if (GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + SentinelNodeRelease(node); + } return nullptr; } else { - if (inserted == false) { + if (!inserted) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Index Insert", "Failed to insert sentinel to index %s", m_name.c_str()); - m_sentinelPool->Release(sentinel); - sentinel = nullptr; + SentinelRelease(sentinel); + if (GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + SentinelNodeRelease(node); + } return nullptr; } + sentinel->SetIndexOrder(m_indexOrder); if (GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { - sentinel->SetPrimaryIndex(); sentinel->SetNextPtr(row); row->SetPrimarySentinel(sentinel); - } else { + } else if (GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY) { MOT_ASSERT(row->GetPrimarySentinel() != nullptr); sentinel->SetNextPtr(row->GetPrimarySentinel()); + } else { + MOT_ASSERT(node != nullptr); + node->Init(0, Sentinel::SENTINEL_INIT_CSN, row->GetPrimarySentinel()); + sentinel->SetNextPtr(node); } MOT_ASSERT(sentinel->IsCommited() == true); return sentinel; @@ -341,7 +409,7 @@ Row* Index::IndexRead(const Key* key, uint32_t pid) const Sentinel* sentinel = IndexReadImpl(key, pid); if (sentinel != nullptr) { row = sentinel->GetData(); - if (row == nullptr || row->IsAbsentRow()) + if (row == nullptr || row->IsRowDeleted()) row = nullptr; } @@ -360,18 +428,15 @@ Sentinel* Index::IndexReadHeader(const Key* key, uint32_t pid) const Sentinel* Index::IndexRemove(const Key* key, uint32_t pid) { Sentinel* sentinel = IndexRemoveImpl(key, pid); + return sentinel; } void Index::Compact(Table* table, uint32_t pid) { IndexIterator* it = nullptr; - char ixPrefix[256]; char sentinelPrefix[256]; - errno_t erc = snprintf_s(ixPrefix, sizeof(ixPrefix), sizeof(ixPrefix) - 1, "%s(key pool)", m_name.c_str()); - securec_check_ss(erc, "\0", "\0"); - ixPrefix[erc] = 0; - erc = snprintf_s( + errno_t erc = snprintf_s( sentinelPrefix, sizeof(sentinelPrefix), sizeof(sentinelPrefix) - 1, "%s(sentinel pool)", m_name.c_str()); securec_check_ss(erc, "\0", "\0"); sentinelPrefix[erc] = 0; @@ -386,6 +451,11 @@ void Index::Compact(Table* table, uint32_t pid) return; } + // return if empty + if (!it->IsValid()) { + break; + } + if (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY) { char tabPrefix[256]; erc = snprintf_s( @@ -396,33 +466,85 @@ void Index::Compact(Table* table, uint32_t pid) chRow.StartCompaction(); - if (!chRow.IsCompactionNeeded()) { - break; - } - - // return if empty - if (it == nullptr || !it->IsValid()) { - break; - } - - // do compaction - while (it->IsValid()) { - Sentinel* ps = it->GetPrimarySentinel(); - Row* row = ps->GetData(); - if (row != nullptr) { - Row* newRow = chRow.CompactObj(row); - if (newRow != nullptr) { - ps->SetNextPtr(newRow); + if (chRow.IsCompactionNeeded()) { + // do compaction + while (it->IsValid()) { + PrimarySentinel* ps = static_cast(it->GetPrimarySentinel()); + Row* head = ps->GetData(); + Row* next = nullptr; + if (head != nullptr and !head->IsRowDeleted()) { + next = head->GetNextVersion(); + Row* newHead = chRow.CompactObj(head); + if (newHead != nullptr) { + ps->SetNextPtr(newHead); + head = newHead; + head->SetNextVersion(nullptr); + } + // Traverse on next + Row* tmp = next; + while (tmp) { + if (tmp->IsRowDeleted()) { + head->SetNextVersion(tmp); + head = tmp; + next = next->GetNextVersion(); + tmp = next; + continue; + } + next = next->GetNextVersion(); + Row* newRow = chRow.CompactObj(tmp); + if (newRow != nullptr) { + head->SetNextVersion(newRow); + head = newRow; + } else { + head->SetNextVersion(tmp); + head = tmp; + } + tmp = next; + } } + + it->Next(); } - it->Next(); } // end compaction chRow.EndCompaction(); + + // Compact TombStone Pool + erc = snprintf_s(tabPrefix, + sizeof(tabPrefix), + sizeof(tabPrefix) - 1, + "%s(tombstone pool)", + table->GetTableName().c_str()); + securec_check_ss(erc, "\0", "\0"); + tabPrefix[erc] = 0; + CompactHandler chTombStone(table->m_tombStonePool, tabPrefix); + chTombStone.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chTombStone.EndCompaction(); } chSentinel.EndCompaction(); + + if (m_sSentinelVersionPool != nullptr) { + erc = snprintf_s(sentinelPrefix, + sizeof(sentinelPrefix), + sizeof(sentinelPrefix) - 1, + "%s(sentinel version pool)", + m_name.c_str()); + securec_check_ss(erc, "\0", "\0"); + sentinelPrefix[erc] = 0; + CompactHandler chSentinelVersion(m_sSentinelVersionPool, sentinelPrefix); + chSentinelVersion.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chSentinelVersion.EndCompaction(); + } + + erc = snprintf_s( + sentinelPrefix, sizeof(sentinelPrefix), sizeof(sentinelPrefix) - 1, "%s(key pool)", m_name.c_str()); + securec_check_ss(erc, "\0", "\0"); + sentinelPrefix[erc] = 0; + CompactHandler chKey(m_keyPool, sentinelPrefix); + chKey.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chKey.EndCompaction(); } while (false); if (it != nullptr) { @@ -431,29 +553,37 @@ void Index::Compact(Table* table, uint32_t pid) } } -uint64_t Index::GetIndexSize() +uint64_t Index::GetIndexSize(uint64_t& netTotal) { - uint64_t res; - uint64_t netto; - PoolStatsSt stats; + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); securec_check(erc, "\0", "\0"); stats.m_type = PoolStatsT::POOL_STATS_ALL; - m_keyPool->GetStats(stats); - res = stats.m_poolCount * stats.m_poolGrossSize; - netto = (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + m_keyPool->PrintStats(stats, "Key Pool", LogLevel::LL_INFO); + uint64_t res = stats.m_poolCount * stats.m_poolGrossSize; + netTotal = (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); securec_check(erc, "\0", "\0"); stats.m_type = PoolStatsT::POOL_STATS_ALL; - m_sentinelPool->GetStats(stats); + m_sentinelPool->PrintStats(stats, "Sentinel Pool", LogLevel::LL_INFO); res += stats.m_poolCount * stats.m_poolGrossSize; - netto += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + netTotal += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; - MOT_LOG_INFO("Index %s memory size: gross: %lu, netto: %lu", m_name.c_str(), res, netto); + if (m_sSentinelVersionPool) { + erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + m_sSentinelVersionPool->GetStats(stats); + m_sSentinelVersionPool->PrintStats(stats, "Sentinel Version Pool", LogLevel::LL_INFO); + res += stats.m_poolCount * stats.m_poolGrossSize; + netTotal += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + } + + MOT_LOG_INFO("Index %s memory size - Gross: %lu, NetTotal: %lu", m_name.c_str(), res, netTotal); return res; } @@ -474,10 +604,25 @@ Index* Index::CloneEmpty() clonedIndex->m_name = m_name; clonedIndex->m_table = m_table; clonedIndex->m_keyPool = ObjAllocInterface::GetObjPool(sizeof(Key) + ALIGN8(m_keyLength), false); - clonedIndex->m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(Sentinel), false); + if (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY) { + clonedIndex->m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinel), false); + } else { + if (m_unique) { + clonedIndex->m_sSentinelVersionPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinelNode), false); + if (clonedIndex->m_sSentinelVersionPool == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Clone Index", "Failed to allocate sentinel object pool for cloned index"); + delete clonedIndex; + return nullptr; + } + clonedIndex->m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinelUnique), false); + } else { + clonedIndex->m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinel), false); + } + } clonedIndex->m_fake = m_fake; clonedIndex->m_indexId = m_indexId; - clonedIndex->m_isCommited = m_isCommited; + clonedIndex->m_isCommited = false; clonedIndex->m_numKeyFields = m_numKeyFields; clonedIndex->m_unique = m_unique; @@ -512,14 +657,26 @@ Index* Index::CloneEmpty() return clonedIndex; } -MOTIndexArr::MOTIndexArr(MOT::Table* table) +void Index::ReclaimSentinel(Sentinel* sentinel) { - m_numIndexes = 0; - m_table = table; - m_rowPool = table->GetRowPool(); - errno_t erc = memset_s(m_indexArr, MAX_NUM_INDEXES * sizeof(MOT::Index*), 0, MAX_NUM_INDEXES * sizeof(MOT::Index*)); - securec_check(erc, "\0", "\0"); - erc = memset_s(m_origIx, MAX_NUM_INDEXES * sizeof(uint16_t), 0, MAX_NUM_INDEXES * sizeof(uint16_t)); - securec_check(erc, "\0", "\0"); + Row* r = nullptr; + switch (GetIndexOrder()) { + case IndexOrder::INDEX_ORDER_PRIMARY: + r = sentinel->GetData(); + while (r) { + Row* reclaimRow = r; + r = r->GetNextVersion(); + reclaimRow->GetTable()->DestroyRow(reclaimRow); + } + break; + case IndexOrder::INDEX_ORDER_SECONDARY: + break; + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + static_cast(sentinel)->ReleaseAllNodes(); + break; + } + + m_sentinelPool->Release(sentinel); } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/index/index.h b/src/gausskernel/storage/mot/core/storage/index/index.h index a69c0cfb0..d3eb54e3a 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index.h +++ b/src/gausskernel/storage/mot/core/storage/index/index.h @@ -30,6 +30,10 @@ #include "key.h" #include "index_iterator.h" #include "txn.h" +#include "sentinel.h" +#include "primary_sentinel.h" +#include "secondary_sentinel.h" +#include "secondary_sentinel_unique.h" #include "object_pool.h" #include "utilities.h" @@ -52,11 +56,15 @@ protected: * @param indexing_method The method used for indexing (hash or tree). */ Index(IndexOrder indexOrder, IndexingMethod indexingMethod) - : m_indexOrder(indexOrder), + : m_keyLength(0), + m_indexOrder(indexOrder), m_indexingMethod(indexingMethod), m_indexExtId(0), + m_startCSN(-1), + m_indexId(0), m_keyPool(nullptr), m_sentinelPool(nullptr), + m_sSentinelVersionPool(nullptr), m_table(nullptr) {} @@ -68,14 +76,22 @@ public: { if (m_keyPool != nullptr) { ObjAllocInterface::FreeObjPool(&m_keyPool); + m_keyPool = nullptr; } if (m_sentinelPool != nullptr) { ObjAllocInterface::FreeObjPool(&m_sentinelPool); + m_sentinelPool = nullptr; + } + if (m_sSentinelVersionPool != nullptr) { + ObjAllocInterface::FreeObjPool(&m_sSentinelVersionPool); + m_sSentinelVersionPool = nullptr; } if (m_colBitmap != nullptr) { delete[] m_colBitmap; m_colBitmap = nullptr; } + + m_table = nullptr; } Index* CloneEmpty(); @@ -99,8 +115,10 @@ public: if (m_indexExtId == 0) { m_indexExtId = m_indexId; } - if (m_keyLength > MAX_KEY_SIZE) + + if (m_keyLength > MAX_KEY_SIZE) { return RC_INDEX_EXCEEDS_MAX_SIZE; + } m_keyPool = ObjAllocInterface::GetObjPool(sizeof(Key) + ALIGN8(m_keyLength), false); if (m_keyPool == nullptr) { @@ -108,7 +126,23 @@ public: MOT_ERROR_OOM, "Index Initialization", "Failed to allocate key pool for index %s", name.c_str()); return RC_MEMORY_ALLOCATION_ERROR; // safe cleanup during destructor } - m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(Sentinel), false); + if (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY) { + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinel), false); + } else { + if (m_unique) { + m_sSentinelVersionPool = ObjAllocInterface::GetObjPool(sizeof(PrimarySentinelNode), false); + if (m_sSentinelVersionPool == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Index Initialization", + "Failed to allocate sentinel pool for index %s", + name.c_str()); + return RC_MEMORY_ALLOCATION_ERROR; // safe cleanup during destructor + } + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinelUnique), false); + } else { + m_sentinelPool = ObjAllocInterface::GetObjPool(sizeof(SecondarySentinel), false); + } + } if (m_sentinelPool == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "Index Initialization", "Failed to allocate sentinel pool for index %s", name.c_str()); @@ -120,8 +154,9 @@ public: inline bool IsKeySizeValid(uint32_t len, bool isUnique) const { len += (isUnique ? 0 : 8); - if (len > MAX_KEY_SIZE) + if (len > MAX_KEY_SIZE) { return false; + } return true; } @@ -134,31 +169,158 @@ public: /** * @brief Clears object pool thread level cache */ - void ClearThreadMemoryCache() + virtual void ClearThreadMemoryCache() { - if (m_keyPool != nullptr) + if (m_keyPool != nullptr) { m_keyPool->ClearThreadCache(); - if (m_sentinelPool != nullptr) - m_sentinelPool->ClearThreadCache(); - } - - void ClearFreeCache() - { - if (m_keyPool != nullptr) - m_keyPool->ClearFreeCache(); - if (m_sentinelPool != nullptr) - m_sentinelPool->ClearFreeCache(); - } - - static uint32_t SentinelDtor(void* buf, void* buf2, bool drop_index) - { - // If drop_index == true, all index's pools are going to be cleaned, so we skip the release here - Sentinel* sent = reinterpret_cast(buf); - Index* ind = sent->GetIndex(); - uint32_t size = ind->m_sentinelPool->m_size; - if (drop_index == false) { - ind->m_sentinelPool->Release(buf); } + if (m_sentinelPool != nullptr) { + m_sentinelPool->ClearThreadCache(); + } + if (m_sSentinelVersionPool != nullptr) { + m_sSentinelVersionPool->ClearThreadCache(); + } + } + + /** + * @brief Clears object pool level cache + */ + virtual void ClearFreeCache() + { + if (m_keyPool != nullptr) { + m_keyPool->ClearFreeCache(); + } + if (m_sentinelPool != nullptr) { + m_sentinelPool->ClearFreeCache(); + } + if (m_sSentinelVersionPool != nullptr) { + m_sSentinelVersionPool->ClearFreeCache(); + } + } + + /** + * @brief Allocate sentinel from memory pool + */ + Sentinel* SentinelAlloc() + { + switch (m_indexOrder) { + case IndexOrder::INDEX_ORDER_PRIMARY: + return m_sentinelPool->Alloc(); + case IndexOrder::INDEX_ORDER_SECONDARY: + return m_sentinelPool->Alloc(); + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + return m_sentinelPool->Alloc(); + default: + break; + } + return nullptr; + } + + /** + * @brief Release sentinel to memory pool + */ + void SentinelRelease(Sentinel* sentinel) + { + switch (m_indexOrder) { + case IndexOrder::INDEX_ORDER_PRIMARY: + m_sentinelPool->Release(static_cast(sentinel)); + break; + case IndexOrder::INDEX_ORDER_SECONDARY: + m_sentinelPool->Release(static_cast(sentinel)); + break; + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + m_sentinelPool->Release(static_cast(sentinel)); + break; + default: + break; + } + } + + void ReclaimSentinel(Sentinel* sentinel); + + /** + * @brief Allocate the sentinel node from the memory pool + * @return pointer to the newly allocated node + */ + PrimarySentinelNode* SentinelNodeAlloc() + { + return m_sSentinelVersionPool->Alloc(); + } + + /** + * @brief Release the sentinel node to the memory pool + * @param node pointer to the node we want to reclaim + * @return + */ + void SentinelNodeRelease(PrimarySentinelNode* node) + { + m_sSentinelVersionPool->Release(node); + } + + /** + * @brief GC callback function for sentinel reclamation + * @param gcElement element with all the necessary meta-data + * @param oper Current GC operation + * @param unused part of the function pointer signature + * @return The name of the index. + */ + static uint32_t SentinelDtor(void* gcElement, void* oper, void* aux) + { + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); + LimboElement* elem = reinterpret_cast(gcElement); + // If drop_index == true, all index's pools are going to be cleaned, so we skip the release here + Sentinel* sent = reinterpret_cast(elem->m_objectPtr); + Index* ind = sent->GetIndex(); + uint32_t gcSlot = 0; + uint32_t size = ind->m_sentinelPool->m_size; + if (sent->IsPrimaryIndex()) { + PrimarySentinel* ps = reinterpret_cast(sent); + gcSlot = ps->GetGcInfo().RefCountUpdate(DEC); + if ((gcSlot == 1) and ps->IsSentinelRemovable()) { + if (gcOperType != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + MOT_LOG_DEBUG( + "SentinelDtor Deleting %p startCSN %lu CSN %lu ", (void*)sent, ps->GetStartCSN(), elem->m_csn); + ind->ReclaimSentinel(sent); + } else { + MOT_LOG_DEBUG( + "SentinelDtor Skipping %p startCSN %lu CSN %lu", (void*)sent, ps->GetStartCSN(), elem->m_csn); + } + } + } else { + if (gcOperType != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + ind->ReclaimSentinel(sent); + } + } + return size; + } + + /** + * @brief a callback function to remove and destroy a deleted version chain. + * @param gcElement A place holder for the gc element metadata. + * @param oper GC operation for the current element + * @param aux a pointer to the GC delete vector + * @return size of cleaned element + */ + static uint32_t DeleteKeyDtor(void* gcElement, void* oper, void* aux) + { + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); + LimboElement* elem = reinterpret_cast(gcElement); + GcQueue::DeleteVector* deleteVector = static_cast(aux); + // If drop_index == true, all index's pools are going to be cleaned, so we skip the release here + Sentinel* sentinel = reinterpret_cast(elem->m_objectPtr); + Key* key = reinterpret_cast(elem->m_objectPool); + Index* index_ = sentinel->GetIndex(); + uint32_t size = index_->m_sentinelPool->m_size; + if (unlikely(gcOperType == GC_OPERATION_TYPE::GC_OPER_DROP_INDEX)) { + return size; + } + RC rc = sentinel->RefCountUpdate(DEC); + if (rc == RC::RC_INDEX_DELETE) { + Sentinel* outputSen = index_->IndexRemove(key, 0); + MOT_ASSERT(outputSen == sentinel); + deleteVector->push_back(outputSen); + } + index_->DestroyKey(key); return size; } @@ -213,7 +375,7 @@ public: /** * @brief Re-initialize index back to empty and compacted one. */ - virtual RC ReInitIndex() = 0; + virtual RC ReInitIndex(bool isDrop) = 0; // Iterator API /** @@ -500,7 +662,7 @@ public: return ((m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY && m_fake) ? true : false); } - inline bool IsUnique() + inline bool IsUnique() const { return m_unique; } @@ -515,25 +677,53 @@ public: return m_isCommited; } + inline void SetSnapshot(uint64_t csn) + { + m_startCSN = csn; + } + + inline uint64_t GetSnapshot() const + { + return m_startCSN; + } + inline bool SetNumTableFields(uint32_t num) { - bool result = false; - m_colBitmap = new (std::nothrow) uint8_t[BITMAP_GETLEN(num)]{0}; + int bLen = BITMAP_GETLEN(num); + int oldLen = BITMAP_GETLEN(m_numTableFields); + uint8_t* tmp = nullptr; + bool needAlloc = false; if (m_colBitmap != nullptr) { - m_numTableFields = num; - result = true; + if (bLen != oldLen) { + needAlloc = true; + } } else { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Index Initialization", "Failed to allocate %u bytes for null bitmap", num); + needAlloc = true; } - return result; + if (needAlloc) { + tmp = new (std::nothrow) uint8_t[bLen]{0}; + if (tmp == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Index Initialization", "Failed to allocate %u bytes for null bitmap", num); + return false; + } + if (m_colBitmap != nullptr) { + errno_t erc = memcpy_s(tmp, bLen, m_colBitmap, oldLen); + securec_check(erc, "\0", "\0"); + delete[] m_colBitmap; + } + m_colBitmap = tmp; + } + m_numTableFields = num; + return true; } inline void SetNumIndexFields(uint32_t num) { - if (num > MAX_KEY_COLUMNS) + if (num > MAX_KEY_COLUMNS) { return; - else - m_numKeyFields = num; + } + m_numKeyFields = num; } inline bool IsFieldPresent(int16_t colid) const @@ -559,7 +749,8 @@ public: inline void AdjustKey(Key* key, uint8_t pattern) const { if (!m_unique) { - key->FillPattern(pattern, NON_UNIQUE_INDEX_SUFFIX_LEN, m_keyLength - NON_UNIQUE_INDEX_SUFFIX_LEN); + MOT_ASSERT(m_keyLength <= key->GetKeyLength()); + (void)key->FillPattern(pattern, NON_UNIQUE_INDEX_SUFFIX_LEN, m_keyLength - NON_UNIQUE_INDEX_SUFFIX_LEN); } } @@ -598,6 +789,11 @@ public: return m_sentinelPool->m_size; } + inline uint32_t GetSecondarySentinelObjSizeFromPool() const + { + return m_sSentinelVersionPool->m_size; + } + inline void SetUnique(bool unique) { m_unique = unique; @@ -608,11 +804,11 @@ public: return m_unique; } - void Truncate(bool isDrop); + RC Truncate(bool isDrop); - void Compact(Table* table, uint32_t pid); + virtual void Compact(Table* table, uint32_t pid); - virtual uint64_t GetIndexSize(); + virtual uint64_t GetIndexSize(uint64_t& netTotal); // Index API /** @@ -626,7 +822,7 @@ public: * @return A previously existing row already mapped to the specified key or null pointer if a new * row was inserted. */ - bool IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, RC& rc); + bool IndexInsert(Sentinel*& outputSentinel, const Key* key, uint32_t pid, RC& rc, bool isRecovery = false); Sentinel* IndexInsert(const Key* key, Row* row, uint32_t pid); /** @@ -679,6 +875,8 @@ protected: /** @var Specifies external index identifier. */ uint64_t m_indexExtId; + /** @var The creation snapshot of the index */ + uint64_t m_startCSN; /** @var Global atomic index identifier. */ static std::atomic m_indexCounter; @@ -696,6 +894,7 @@ protected: ObjAllocInterface* m_keyPool; ObjAllocInterface* m_sentinelPool; + ObjAllocInterface* m_sSentinelVersionPool; Table* m_table; @@ -730,72 +929,6 @@ protected: DECLARE_CLASS_LOGGER() }; - -/** - * @class MOTIndexArr - * @brief This class contains a temporary copy of index array of the table. - */ -class MOTIndexArr { -public: - explicit MOTIndexArr(MOT::Table* table); - - ~MOTIndexArr() - {} - - MOT::Index* GetIndex(uint16_t ix) - { - if (likely(ix < m_numIndexes)) { - return m_indexArr[ix]; - } - - return nullptr; - } - - uint16_t GetIndexIx(uint16_t ix) - { - if (likely(ix < m_numIndexes)) { - return m_origIx[ix]; - } - - return MAX_NUM_INDEXES; - } - - void Add(uint16_t ix, MOT::Index* index) - { - if (likely(m_numIndexes < MAX_NUM_INDEXES)) { - m_indexArr[m_numIndexes] = index; - m_origIx[m_numIndexes] = ix; - m_numIndexes++; - } - } - - uint16_t GetNumIndexes() - { - return m_numIndexes; - } - - MOT::Table* GetTable() - { - return m_table; - } - - void SetRowPool(MOT::ObjAllocInterface* rowPool) - { - m_rowPool = rowPool; - } - - MOT::ObjAllocInterface* GetRowPool() - { - return m_rowPool; - } - -private: - MOT::Index* m_indexArr[MAX_NUM_INDEXES]; - MOT::Table* m_table; - MOT::ObjAllocInterface* m_rowPool; - uint16_t m_origIx[MAX_NUM_INDEXES]; - uint16_t m_numIndexes; -}; } // namespace MOT #endif /* MOT_INDEX_H */ diff --git a/src/gausskernel/storage/mot/core/storage/index/index_base.h b/src/gausskernel/storage/mot/core/storage/index/index_base.h index 305d77868..296ff0f7d 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index_base.h +++ b/src/gausskernel/storage/mot/core/storage/index/index_base.h @@ -28,7 +28,7 @@ #define MOT_INDEX_BASE_H #include -#include +#include #include #include diff --git a/src/gausskernel/storage/mot/core/storage/index/index_defs.cpp b/src/gausskernel/storage/mot/core/storage/index/index_defs.cpp index cf3be2764..ba4f13b87 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index_defs.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/index_defs.cpp @@ -33,7 +33,5 @@ const char* IndexTreeFlavorToString(const IndexTreeFlavor& indexTreeFlavor) default: return "InvalidFlavor"; } - - return "InvalidFlavor"; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/index/index_defs.h b/src/gausskernel/storage/mot/core/storage/index/index_defs.h index e8d211606..f148db4a3 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index_defs.h +++ b/src/gausskernel/storage/mot/core/storage/index/index_defs.h @@ -120,7 +120,12 @@ enum class IndexOrder : uint32_t { /** * @var Denotes a secondary index. */ - INDEX_ORDER_SECONDARY + INDEX_ORDER_SECONDARY, + + /** + * @var Denotes a secondary unique index. + */ + INDEX_ORDER_SECONDARY_UNIQUE }; /** diff --git a/src/gausskernel/storage/mot/core/storage/index/index_factory.cpp b/src/gausskernel/storage/mot/core/storage/index/index_factory.cpp index cd57385aa..544017adb 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index_factory.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/index_factory.cpp @@ -27,8 +27,6 @@ #include "utilities.h" namespace MOT { -DECLARE_LOGGER(IndexFactory, Storage) - IMPLEMENT_CLASS_LOGGER(IndexFactory, Storage); Index* IndexFactory::CreateIndex(IndexOrder indexOrder, IndexingMethod indexingMethod, IndexTreeFlavor flavor) diff --git a/src/gausskernel/storage/mot/core/storage/index/index_iterator.h b/src/gausskernel/storage/mot/core/storage/index/index_iterator.h index 63c84d2e4..f116bde4b 100644 --- a/src/gausskernel/storage/mot/core/storage/index/index_iterator.h +++ b/src/gausskernel/storage/mot/core/storage/index/index_iterator.h @@ -39,9 +39,7 @@ public: * @brief Destructor. */ virtual ~IndexIterator() - { - Destroy(); - } + {} /** * @@brief Retrieves the type of the iterator. diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree.hpp index 7efde94d9..ac807b77b 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree.hpp @@ -25,8 +25,6 @@ #ifndef MOT_MASSTREE_HPP #define MOT_MASSTREE_HPP -using namespace MOT; - #include "masstree.hh" namespace Masstree { @@ -43,7 +41,6 @@ bool basic_table

::init(const uint16_t keyLength, const std::string& name, des template int basic_table

::getMemtagMaxSize(enum memtag tag) { - int size = 0; switch (tag) { case memtag_masstree_leaf: return MAX_MEMTAG_MASSTREE_LEAF_ALLOCATION_SIZE; @@ -58,8 +55,6 @@ int basic_table

::getMemtagMaxSize(enum memtag tag) default: return -1; } - - return -1; } } // namespace Masstree #endif // MOT_MASSTREE_HPP diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_get.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_get.hpp index 8efdbb166..165ac22bb 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_get.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_get.hpp @@ -38,7 +38,6 @@ void basic_table

::find( unlocked_cursor_type lp(*this, reinterpret_cast(key), ALIGN8(key_len)); found = lp.find_unlocked(*mtSessionThreadInfo); - if (found) { output = reinterpret_cast(lp.value()); } diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_insert.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_insert.hpp index f2a72a239..3823f266d 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_insert.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_insert.hpp @@ -34,7 +34,6 @@ template void* basic_table

::insert( const uint8_t* key, const uint32_t key_len, void* const& entry, bool& result, const uint32_t& pid) { - MOT_LOG_DEBUG("table: %s", name_.c_str()); // This should be optimized at compile time by bitshifts and using ctz cursor_type lp(*this, key, ALIGN8(key_len)); @@ -77,9 +76,6 @@ void* basic_table

::insert( // If the key is new (not previously existing) then we record the entry under // that key if (!found) { -#if ISOLATION_LEVEL == SERIALIZABLE - handler->observe_phantoms(lp.node()); -#endif lp.value() = reinterpret_cast(entry); } else { // If the insertion was successful, we return nullptr (the sentinel was diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_iterator.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_iterator.hpp index 6a3359d5b..ef721417c 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_iterator.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_iterator.hpp @@ -30,6 +30,8 @@ #include "key.h" namespace Masstree { +using namespace MOT; + template void basic_table

::iteratorScan(const char* keybuf, uint32_t keylen, const bool& matchKey, void* const& it, const bool& forwardDirection, bool& result, const uint32_t& pid) @@ -89,10 +91,6 @@ private: /** @var Search key (in Masstree's key format). */ key_type m_key; - /** @var Masstree instance (basic_table

) pointer. */ - // IDAN: TODO: Not is use. Check performance after removing t - table_type* m_table; - /** @var Thread info pointer. */ threadinfo_type* m_ti; @@ -127,7 +125,6 @@ private: { errno_t erc; m_searchKey = &m_motKey; - m_table = table; m_ti = ti; m_stack.root_ = table->root_; m_entry = leafvalue_type::make_empty(); @@ -162,12 +159,13 @@ private: bool found = false; while (true) { m_state = m_stack.find_initial(m_helper, m_key, m_matchKey, found, m_entry, *m_ti); - if (m_state != mystack_type::scan_down) + if (m_state != mystack_type::scan_down) { break; + } m_key.shift(); } m_foundInitial = true; - Next(); + (void)Next(); return found; } @@ -227,7 +225,7 @@ public: /** * @brief Default constructor. */ - MasstreeIterator(void) : m_table(nullptr), m_ti(nullptr), m_state(0), m_done(true) + MasstreeIterator(void) : m_ti(nullptr), m_state(0), m_done(true), m_foundInitial(false), m_matchKey(false) {} /** @@ -235,6 +233,7 @@ public: */ ~MasstreeIterator(void) { + m_ti = nullptr; m_searchKey = nullptr; } @@ -255,9 +254,9 @@ public: if (m_foundInitial) { m_stack.ki_ = m_helper.next(m_stack.ki_); m_state = m_stack.find_next(m_helper, m_key, m_entry); - Next(); + (void)Next(); } else { - Begin(); + (void)Begin(); } return this; } @@ -289,9 +288,7 @@ public: * @brief Destroy instance */ void Destroy(void) - { - delete this; - } + {} /** @brief Getter for searchKey pointer * @return SearchKey (if valid) or null if not. diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_kvthread.cpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_kvthread.cpp index 6ea36182c..ca514e329 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_kvthread.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_kvthread.cpp @@ -29,22 +29,40 @@ #include #include #include -#if HAVE_SUPERPAGE && !NOSUPERPAGE +#if HAVE_SUPERPAGE +#ifndef NOSUPERPAGE #include #include #endif +#endif #include "mm_api.h" #include "mm_gc_manager.h" +using namespace MOT; + // This is the thread info which serves the current masstree operation. It is set before the operation starts. __thread threadinfo* mtSessionThreadInfo = nullptr; +class MOTThreadInfoDestructor { +public: + MOTThreadInfoDestructor() + {} + ~MOTThreadInfoDestructor() + { + if (mtSessionThreadInfo != nullptr) { + (void)threadinfo::make( + mtSessionThreadInfo, threadinfo::TI_PROCESS, MOTCurrThreadId, -1 /* Destroy object */); + mtSessionThreadInfo = nullptr; + } + } +}; + volatile mrcu_epoch_type globalepoch; inline threadinfo::threadinfo(int purpose, int index, int rcu_max_free_count) { - errno_t erc = memset_s(this, sizeof(*this), 0, sizeof(*this)); + errno_t erc = memset_s(static_cast(this), sizeof(*this), 0, sizeof(*this)); securec_check(erc, "\0", "\0"); purpose_ = purpose; @@ -52,6 +70,9 @@ inline threadinfo::threadinfo(int purpose, int index, int rcu_max_free_count) rcu_free_count = rcu_max_free_count; ts_ = 2; + limbo_head_ = limbo_tail_ = nullptr; + gc_session_ = nullptr; + cur_working_index = nullptr; } // if rcu_max_free_count == -1, destroy threadinfo structure @@ -78,10 +99,9 @@ threadinfo* threadinfo::make(void* obj_mem, int purpose, int index, int rcu_max_ return nullptr; } - ti->mark(tc_limbo_slots, mt_limbo_group::capacity); ti->limbo_head_ = ti->limbo_tail_ = new (limbo_space) mt_limbo_group; } - + thread_local MOTThreadInfoDestructor motThreadInfoDest; return ti; } @@ -100,7 +120,6 @@ void* threadinfo::allocate(size_t sz, memtag tag, size_t* actual_size) if (actual_size) { *actual_size = size; } - mark(threadcounter(tc_alloc + (tag > memtag_value)), sz); } return p; } @@ -110,19 +129,17 @@ void threadinfo::deallocate(void* p, size_t sz, memtag tag) MOT_ASSERT(p); p = memdebug::check_free(p, sz, tag); if (likely(!use_pool())) { - ((MasstreePrimaryIndex*)cur_working_index)->DeallocateMem(p, sz, tag); + (void)((MasstreePrimaryIndex*)cur_working_index)->DeallocateMem(p, sz, tag); } else { free(p); } - mark(threadcounter(tc_alloc + (tag > memtag_value)), -sz); } void threadinfo::ng_record_rcu(void* p, int sz, memtag tag) { MOT_ASSERT(p); memdebug::check_rcu(p, sz, tag); - ((MasstreePrimaryIndex*)cur_working_index)->RecordMemRcu(p, sz, tag); - mark(threadcounter(tc_alloc + (tag > memtag_value)), -sz); + (void)((MasstreePrimaryIndex*)cur_working_index)->RecordMemRcu(p, sz, tag); } // MOT is using MOT::GcManager class to manage gc_session @@ -131,7 +148,7 @@ void threadinfo::set_gc_session(void* gc_session) gc_session_ = gc_session; } -inline void* threadinfo::get_gc_session() +void* threadinfo::get_gc_session() { return gc_session_; } diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_remove.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_remove.hpp index 89ce332c6..b7ed9d39b 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_remove.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_remove.hpp @@ -42,7 +42,7 @@ struct gc_layer_rcu_callback_ng : public P::threadinfo_type::mrcu_callback { size_t size_; MOT::MasstreePrimaryIndex* index_; char s_[0]; - gc_layer_rcu_callback_ng(node_base

** root_ref, Str prefix, size_t size) + gc_layer_rcu_callback_ng(node_base

** root_ref, const Str& prefix, size_t size) : root_ref_(root_ref), len_(prefix.length()), size_(size), @@ -58,7 +58,7 @@ struct gc_layer_rcu_callback_ng : public P::threadinfo_type::mrcu_callback { return size_; } - static void make(node_base

** root_ref, Str prefix, threadinfo& ti); + static void make(node_base

** root_ref, const Str& prefix, threadinfo& ti); }; template @@ -66,9 +66,9 @@ size_t gc_layer_rcu_callback_ng

::operator()(bool drop_index) { // If drop_index == true, all index's pools are going to be cleaned, so we can skip gc_layer call (which might add // more elements into GC) - if (drop_index == false) { - // GC layer remove might delete elements from tree and might create new gc layer removal requests and add them - // to GC. Index must be provided to allow access to the memory pools. + if (!drop_index) { + // GC layer remove might delete elements from tree or create new gc layer removal requests and add them to GC. + // Index must be provided to allow access to the memory pools. mtSessionThreadInfo->set_working_index(index_); (*this)(*mtSessionThreadInfo); mtSessionThreadInfo->set_working_index(NULL); @@ -91,7 +91,7 @@ void gc_layer_rcu_callback_ng

::operator()(threadinfo& ti) } template -void gc_layer_rcu_callback_ng

::make(node_base

** root_ref, Str prefix, threadinfo& ti) +void gc_layer_rcu_callback_ng

::make(node_base

** root_ref, const Str& prefix, threadinfo& ti) { size_t sz = prefix.len + sizeof(gc_layer_rcu_callback_ng

); // As we are using slab allocator for allocation, sz is will be updated by ti.allocate with the real allocation diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_struct.hpp b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_struct.hpp index fdc03803f..c017b2118 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_struct.hpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree/mot_masstree_struct.hpp @@ -30,7 +30,7 @@ namespace Masstree { class key_unparse_printable_string { public: template - static int unparse_key(key key, char* buf, int buflen) + static int unparse_key(const key& key, char* buf, int buflen) { String s = key.unparse().printable(); int cplen = std::min(s.length(), buflen); @@ -54,24 +54,25 @@ template <> class value_print { public: static void print( - unsigned char* value, FILE* f, const char* prefix, int indent, Str key, kvtimestamp_t, char* suffix) + unsigned char* value, FILE* f, const char* prefix, int indent, const Str& key, kvtimestamp_t, char* suffix) { - fprintf(f, "%s%*s%.*s = %p%s\n", prefix, indent, "", key.len, key.s, value, suffix); + (void)fprintf(f, "%s%*s%.*s = %p%s\n", prefix, indent, "", key.len, key.s, value, suffix); } }; template <> class value_print { public: - static void print(uint64_t value, FILE* f, const char* prefix, int indent, Str key, kvtimestamp_t, char* suffix) + static void print( + uint64_t value, FILE* f, const char* prefix, int indent, const Str& key, kvtimestamp_t, char* suffix) { - fprintf(f, "%s%*s%.*s = %" PRIu64 "%s\n", prefix, indent, "", key.len, key.s, value, suffix); + (void)fprintf(f, "%s%*s%.*s = %" PRIu64 "%s\n", prefix, indent, "", key.len, key.s, value, suffix); } }; class key_unparse_unsigned { public: - static int unparse_key(Masstree::key key, char* buf, int buflen) + static int unparse_key(const Masstree::key& key, char* buf, int buflen) { errno_t erc = snprintf_s(buf, buflen, buflen - 1, "%" PRIu64, key.ikey()); securec_check_ss(erc, "\0", "\0"); diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree_index.cpp b/src/gausskernel/storage/mot/core/storage/index/masstree_index.cpp index 17bfdadba..adfe9538f 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree_index.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/masstree_index.cpp @@ -24,6 +24,7 @@ #include "masstree_index.h" #include "mot_engine.h" +#include "object_pool_compact.h" namespace MOT { typedef MasstreePrimaryIndex::IndexImpl PrimaryMasstree; @@ -77,7 +78,7 @@ Sentinel* MasstreePrimaryIndex::IndexInsertImpl(const Key* key, Sentinel* sentin mtSessionThreadInfo->set_working_index(NULL); if (!inserted && existingItem) { // key mapping already exists in unique index - result = reinterpret_cast(existingItem); + result = static_cast(existingItem); } // otherwise return null pointer (if !inserted && !existingItem, Key does not exist and insertation failed due to // memory issue) @@ -94,7 +95,7 @@ Sentinel* MasstreePrimaryIndex::IndexReadImpl(const Key* key, uint32_t pid) cons m_index.find(key->GetKeyBuf(), key->GetKeyLength(), output, result, pid); if (result) { - sentinel = reinterpret_cast(output); + sentinel = static_cast(output); } return sentinel; @@ -119,50 +120,63 @@ Sentinel* MasstreePrimaryIndex::IndexRemoveImpl(const Key* key, uint32_t pid) mtSessionThreadInfo->set_working_index(NULL); if (result) { - sentinel = reinterpret_cast(output); + sentinel = static_cast(output); } return sentinel; } -uint64_t MasstreePrimaryIndex::GetIndexSize() +uint64_t MasstreePrimaryIndex::GetIndexSize(uint64_t& netTotal) { PoolStatsSt stats; + uint64_t res = Index::GetIndexSize(netTotal); + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); securec_check(erc, "\0", "\0"); stats.m_type = PoolStatsT::POOL_STATS_ALL; - m_keyPool->GetStats(stats); - uint64_t res = stats.m_poolCount * stats.m_poolGrossSize; - uint64_t netto = (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; - - erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); - securec_check(erc, "\0", "\0"); - stats.m_type = PoolStatsT::POOL_STATS_ALL; - m_sentinelPool->GetStats(stats); - res += stats.m_poolCount * stats.m_poolGrossSize; - netto += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; - - erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); - securec_check(erc, "\0", "\0"); - stats.m_type = PoolStatsT::POOL_STATS_ALL; m_leafsPool->GetStats(stats); + m_leafsPool->PrintStats(stats, "Leafs Pool", LogLevel::LL_INFO); res += stats.m_poolCount * stats.m_poolGrossSize; - netto += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + netTotal += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); securec_check(erc, "\0", "\0"); stats.m_type = PoolStatsT::POOL_STATS_ALL; m_internodesPool->GetStats(stats); + m_internodesPool->PrintStats(stats, "Internodes Pool", LogLevel::LL_INFO); res += stats.m_poolCount * stats.m_poolGrossSize; - netto += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + netTotal += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; - m_ksuffixSlab->GetSize(res, netto); + m_ksuffixSlab->Print("Ksuffix Slab", LogLevel::LL_INFO); + m_ksuffixSlab->GetSize(res, netTotal); - MOT_LOG_INFO("Index %s memory size: gross: %lu, netto: %lu", m_name.c_str(), res, netto); + MOT_LOG_INFO("Masstree Index %s memory size - Gross: %lu, NetTotal: %lu", m_name.c_str(), res, netTotal); return res; } +void MasstreePrimaryIndex::Compact(Table* table, uint32_t pid) +{ + Index::Compact(table, pid); + + char prefix[256]; + errno_t erc = snprintf_s(prefix, sizeof(prefix), sizeof(prefix) - 1, "%s(leafs pool)", m_name.c_str()); + securec_check_ss(erc, "\0", "\0"); + prefix[erc] = 0; + CompactHandler chLeafs(m_leafsPool, prefix); + chLeafs.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chLeafs.EndCompaction(); + + erc = snprintf_s(prefix, sizeof(prefix), sizeof(prefix) - 1, "%s(internodes pool)", m_name.c_str()); + securec_check_ss(erc, "\0", "\0"); + prefix[erc] = 0; + CompactHandler chInternodes(m_internodesPool, prefix); + chInternodes.StartCompaction(CompactTypeT::COMPACT_SIMPLE); + chInternodes.EndCompaction(); + + m_ksuffixSlab->Compact(); +} + // Iterator API IndexIterator* MasstreePrimaryIndex::Begin(uint32_t pid, bool passive) const { @@ -171,7 +185,7 @@ IndexIterator* MasstreePrimaryIndex::Begin(uint32_t pid, bool passive) const return Search(minKey, /* search key */ 0, /* key size. Ignored if key is null */ - true, /* match key*/ + true, /* match key */ true, /* Forward */ pid, /* pid */ result, /* found */ @@ -181,13 +195,7 @@ IndexIterator* MasstreePrimaryIndex::Begin(uint32_t pid, bool passive) const IndexIterator* MasstreePrimaryIndex::Search( const Key* key, bool matchKey, bool forward, uint32_t pid, bool& found, bool passive) const { - return Search(reinterpret_cast(key->GetKeyBuf()), - ALIGN8(key->GetKeyLength()), - matchKey, - forward, - pid, - found, - passive); + return Search((const char*)(key->GetKeyBuf()), ALIGN8(key->GetKeyLength()), matchKey, forward, pid, found, passive); } IndexIterator* MasstreePrimaryIndex::Search( diff --git a/src/gausskernel/storage/mot/core/storage/index/masstree_index.h b/src/gausskernel/storage/mot/core/storage/index/masstree_index.h index a6e78ae40..4c2829f02 100644 --- a/src/gausskernel/storage/mot/core/storage/index/masstree_index.h +++ b/src/gausskernel/storage/mot/core/storage/index/masstree_index.h @@ -36,9 +36,10 @@ #include "masstree/mot_masstree_iterator.hpp" #include "masstree/mot_masstree_struct.hpp" #include "masstree/mot_masstree_iterator.hpp" -#include #include "mot_engine.h" +#include + static_assert(MASSTREE_MAXKEYLEN == MAX_KEY_SIZE, "MASSTREE_MAXKEYLEN must be equal to MAX_KEY_SIZE"); namespace MOT { @@ -91,7 +92,7 @@ private: * @brief Constructor. * @param itr The underlying index iterator. */ - MTIterator(ItrType* itr) + explicit MTIterator(ItrType* itr) : IndexIterator(IT, true), // always bidirectional m_itr(itr) {} @@ -99,7 +100,7 @@ private: /** * @brief Destructor. */ - virtual ~MTIterator() + ~MTIterator() override { if (m_itr) { delete m_itr; @@ -112,7 +113,7 @@ private: * points to a valid index items and it has not been invalidated due to concurrent modification. * @return True if the iterator is valid. */ - virtual bool IsValid() const + bool IsValid() const override { return !m_itr->Exhausted(); } @@ -120,16 +121,22 @@ private: /** * @brief Invalidates the iterator such that subsequent calls to isValid() return false. */ - virtual void Invalidate() + void Invalidate() override { m_itr->Invalidate(); } + /** + * @brief Performs any implementation-specific cleanup. + */ + void Destroy() override + {} + /** * @brief Retrieves the key of the currently iterated item. * @return A pointer to the key of the currently iterated item. */ - virtual const void* GetKey() const + const void* GetKey() const override { return m_itr->GetSearchKey(); } @@ -138,7 +145,7 @@ private: * @brief Retrieves the row of the currently iterated item. * @return A pointer to the row of the currently iterated item. */ - virtual Row* GetRow() const + Row* GetRow() const override { return GetSentinel()->GetData(); } @@ -147,7 +154,7 @@ private: * @brief Retrieves the currently iterated primary sentinel. * @return The primary sentinel. */ - virtual Sentinel* GetPrimarySentinel() const + Sentinel* GetPrimarySentinel() const override { return const_cast(GetSentinel()); } @@ -155,16 +162,16 @@ private: /** * @brief Moves forwards the iterator to the next item. */ - virtual void Next() + void Next() override { - ++(*m_itr); + (void)++(*m_itr); } /** * @brief Moves backwards the iterator to the previous item. * @detail Does not supported yet. */ - virtual void Prev() + void Prev() override { MOT_ASSERT(false); } @@ -174,7 +181,7 @@ private: * @param rhs The index iterator with which to compare this iterator. * @return True if iterators point to the same index item, otherwise false. */ - virtual bool Equals(const IndexIterator* rhs) const + bool Equals(const IndexIterator* rhs) const override { return **m_itr == **(static_cast(rhs)->m_itr); } @@ -185,7 +192,7 @@ private: * @param serializeFunc The serialization function. * @param buff The buffer into which the iterator is to be serialized. */ - virtual void Serialize(serialize_func_t serializeFunc, unsigned char* buff) const + void Serialize(serialize_func_t serializeFunc, unsigned char* buff) const override {} /** @@ -194,7 +201,7 @@ private: * @param deserializeFunc The deserialization function. * @param buff The buffer from which the iterator is to be deserialized. */ - virtual void Deserialize(deserialize_func_t deserializeFunc, unsigned char* buff) + void Deserialize(deserialize_func_t deserializeFunc, unsigned char* buff) override {} private: @@ -222,90 +229,74 @@ public: /** * @brief Destructor. */ - virtual ~MasstreePrimaryIndex() + ~MasstreePrimaryIndex() override { if (m_initialized) { m_initialized = false; DestroyPools(); } + + m_leafsPool = nullptr; + m_internodesPool = nullptr; + m_ksuffixSlab = nullptr; } /** * @brief Calculate the Index memory consumption. * @return The amount of memory the Index consumes. */ - virtual uint64_t GetIndexSize() override; + uint64_t GetIndexSize(uint64_t& netTotal) override; /** * @brief Retrieves the number of rows stored in the index. This may be an estimation. * @detail Not implemented. * @return The number of rows stored in the index. */ - virtual uint64_t GetSize() const + uint64_t GetSize() const override { return 0; } /** - * @brief Init Masstree memory pools. - * @return True if succeeded otherwise false. - * @note In case of failure it is the responsibility of the caller to call @ref DestroyPools(). + * @brief Clears object pool thread level cache */ - virtual bool InitPools() + void ClearThreadMemoryCache() override { - m_leafsPool = - ObjAllocInterface::GetObjPool(m_index.getMemtagMaxSize(memtag_masstree_leaf), false, CACHE_LINE_SIZE); - if (!m_leafsPool) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create leaf pool"); - return false; // safe cleanup in DestroyPools() + Index::ClearThreadMemoryCache(); + if (m_leafsPool != nullptr) { + m_leafsPool->ClearThreadCache(); } - - m_internodesPool = - ObjAllocInterface::GetObjPool(m_index.getMemtagMaxSize(memtag_masstree_internode), false, CACHE_LINE_SIZE); - if (!m_internodesPool) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create inter-node pool"); - return false; // safe cleanup in DestroyPools() + if (m_internodesPool != nullptr) { + m_internodesPool->ClearThreadCache(); } - - m_ksuffixSlab = new (std::nothrow) SlabAllocator( - SUFFIX_SLAB_MIN_BIN, std::ceil(std::log2(m_index.getMemtagMaxSize(memtag_masstree_ksuffixes))), false); - if (!m_ksuffixSlab) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create suffix allocator"); - return false; // safe cleanup in DestroyPools() + if (m_ksuffixSlab != nullptr) { + m_ksuffixSlab->ClearThreadCache(); } - - if (!m_ksuffixSlab->IsSlabInitialized()) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create suffix allocator"); - return false; // safe cleanup in DestroyPools() - } - - return true; } /** - * @brief Destroy Masstree memory pools. + * @brief Clears object pool level cache */ - virtual void DestroyPools() + void ClearFreeCache() override { - if (m_leafsPool) { - ObjAllocInterface::FreeObjPool(&m_leafsPool); - m_leafsPool = NULL; + Index::ClearFreeCache(); + if (m_leafsPool != nullptr) { + m_leafsPool->ClearFreeCache(); } - if (m_internodesPool) { - ObjAllocInterface::FreeObjPool(&m_internodesPool); - m_internodesPool = NULL; + if (m_internodesPool != nullptr) { + m_internodesPool->ClearFreeCache(); } - - if (m_ksuffixSlab) { - delete m_ksuffixSlab; - m_ksuffixSlab = NULL; + if (m_ksuffixSlab != nullptr) { + m_ksuffixSlab->ClearFreeCache(); } } + void Compact(Table* table, uint32_t pid) override; + /** * @brief Print Masstree pools memory consumption details to log. */ - virtual void PrintPoolsStats(LogLevel level = LogLevel::LL_DEBUG) + void PrintPoolsStats(LogLevel level) { m_leafsPool->Print("Leafs pool", level); m_internodesPool->Print("Internode pool", level); @@ -342,21 +333,26 @@ public: /** * @brief Destroy all memory pools and init index again. */ - virtual RC ReInitIndex() + RC ReInitIndex(bool isDrop) override { m_initialized = false; DestroyPools(); + // remove masstree's root pointer (not valid anymore) *(m_index.root_ref()) = nullptr; - return IndexInitImpl(NULL); + if (isDrop) { + return RC_OK; + } else { + return IndexInitImpl(NULL); + } } // Iterator API - virtual IndexIterator* Begin(uint32_t pid, bool passive = false) const; + IndexIterator* Begin(uint32_t pid, bool passive) const override; - virtual IndexIterator* Search( - const Key* key, bool matchKey, bool forward, uint32_t pid, bool& found, bool passive = false) const; + IndexIterator* Search( + const Key* key, bool matchKey, bool forward, uint32_t pid, bool& found, bool passive) const override; /** * @brief Allocate memory from pools. @@ -378,11 +374,9 @@ public: return m_ksuffixSlab->Alloc(size); default: - MOT_LOG_ERROR("Try to allocating size %d with unknown memtag %d\n", size, tag); + MOT_LOG_ERROR("Try to allocating size %d with unknown memtag %d", size, tag); return NULL; } - - return NULL; } /** @@ -409,11 +403,9 @@ public: return true; default: - MOT_LOG_ERROR("Try to deallocating ptr %p size %d with unknown memtag %d\n", ptr, size, tag); + MOT_LOG_ERROR("Try to deallocating ptr %p size %d with unknown memtag %d", ptr, size, tag); return false; } - - return false; } /** @@ -423,13 +415,15 @@ public: * @param dropIndex Indicates if this callback is part of drop index process. * @return Size of memory that was deallocated. */ - static uint32_t DeallocateFromPoolCallBack(void* pool, void* ptr, bool dropIndex) + static uint32_t DeallocateFromPoolCallBack(void* gcElement, void* oper, void* aux) { + LimboElement* elem = reinterpret_cast(gcElement); + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); // If dropIndex == true, all index's pools are going to be cleaned, so we skip the release here - ObjAllocInterface* localPoolPtr = (ObjAllocInterface*)pool; + ObjAllocInterface* localPoolPtr = (ObjAllocInterface*)elem->m_objectPtr; - if (dropIndex == false) { - localPoolPtr->Release(ptr); + if (gcOperType != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + localPoolPtr->Release(elem->m_objectPool); } return localPoolPtr->m_size; } @@ -441,13 +435,15 @@ public: * @param dropIndex Indicates if this callback is part of drop index process. * @return Size of memory that was deallocated. */ - static uint32_t DeallocateFromSlabCallBack(void* slab, void* ptr, bool dropIndex) + static uint32_t DeallocateFromSlabCallBack(void* gcElement, void* oper, void* aux) { + LimboElement* elem = reinterpret_cast(gcElement); + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); // If dropIndex == true, all index's pools are going to be cleaned, so we skip the release here - void* ptrToFree = (void*)(((uint64_t)ptr) & (uint64_t)0x0000FFFFFFFFFFFF); - int size = (((uint64_t)ptr) & 0xFFFF000000000000) >> 48; - if (dropIndex == false) { - ((SlabAllocator*)slab)->Release(ptrToFree, size); + void* ptrToFree = (void*)(((uint64_t)elem->m_objectPool) & (uint64_t)0x0000FFFFFFFFFFFF); + int size = (((uint64_t)elem->m_objectPool) & 0xFFFF000000000000) >> 48; + if (gcOperType != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + ((SlabAllocator*)elem->m_objectPtr)->Release(ptrToFree, size); } return size; } @@ -459,20 +455,19 @@ public: * @param dropIndex Indicates if this callback is part of drop index process. * @return Size of memory that was deallocated. */ - static uint32_t DeallocateFromSlabGcCallBack(void* slab, void* gcRemoveLayerFuncObjPtr, bool dropIndex) + static uint32_t DeallocateFromSlabGcCallBack(void* gcElement, void* oper, void* aux) { + LimboElement* elem = reinterpret_cast(gcElement); + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); // If dropIndex == true, all index's pools are going to be cleaned, so we skip the release here mtSessionThreadInfo->set_gc_session(GetCurrentGcSession()); - GcEpochType local_epoch = - GetSessionManager()->GetCurrentSessionContext()->GetTxnManager()->GetGcSession()->GcStartInnerTxn(); + bool dropIndex = (gcOperType == GC_OPERATION_TYPE::GC_OPER_DROP_INDEX); - size_t allocationSize = (*static_cast(gcRemoveLayerFuncObjPtr))(dropIndex); - - if (dropIndex == false) { - ((SlabAllocator*)slab)->Release(gcRemoveLayerFuncObjPtr, allocationSize); + size_t allocationSize = (*static_cast(elem->m_objectPool))(dropIndex); + if (gcOperType != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + ((SlabAllocator*)elem->m_objectPtr)->Release(elem->m_objectPool, allocationSize); } - GetSessionManager()->GetCurrentSessionContext()->GetTxnManager()->GetGcSession()->GcEndInnerTxn(false); mtSessionThreadInfo->set_gc_session(NULL); return allocationSize; } @@ -491,32 +486,47 @@ public: MOT_ASSERT(gc_session); switch (tag) { case memtag_masstree_leaf: - gc_session->GcRecordObject( - GetIndexId(), (void*)m_leafsPool, ptr, DeallocateFromPoolCallBack, m_leafsPool->m_size); + gc_session->GcRecordObject(GC_QUEUE_TYPE::GENERIC_QUEUE, + GetIndexId(), + (void*)m_leafsPool, + ptr, + DeallocateFromPoolCallBack, + m_leafsPool->m_size); return true; case memtag_masstree_internode: - gc_session->GcRecordObject( - GetIndexId(), (void*)m_internodesPool, ptr, DeallocateFromPoolCallBack, m_internodesPool->m_size); + gc_session->GcRecordObject(GC_QUEUE_TYPE::GENERIC_QUEUE, + GetIndexId(), + (void*)m_internodesPool, + ptr, + DeallocateFromPoolCallBack, + m_internodesPool->m_size); return true; case memtag_masstree_ksuffixes: MOT_ASSERT((size >> 16) == 0); // validate that size using 2 bytes or less ptrToFree = (void*)((uint64_t)ptr | ((uint64_t)size << 48)); - gc_session->GcRecordObject( - GetIndexId(), (void*)m_ksuffixSlab, ptrToFree, DeallocateFromSlabCallBack, size); + gc_session->GcRecordObject(GC_QUEUE_TYPE::GENERIC_QUEUE, + GetIndexId(), + (void*)m_ksuffixSlab, + ptrToFree, + DeallocateFromSlabCallBack, + size); return true; case memtag_masstree_gc: - gc_session->GcRecordObject(GetIndexId(), (void*)m_ksuffixSlab, ptr, DeallocateFromSlabGcCallBack, size); + gc_session->GcRecordObject(GC_QUEUE_TYPE::GENERIC_QUEUE, + GetIndexId(), + (void*)m_ksuffixSlab, + ptr, + DeallocateFromSlabGcCallBack, + size); return true; default: - MOT_LOG_ERROR("Try to record rcu ptr %p size %d with unknown memtag %d\n", ptr, size, tag); + MOT_LOG_ERROR("Try to record rcu ptr %p size %d with unknown memtag %d", ptr, size, tag); return false; } - - return false; } /** @@ -556,6 +566,63 @@ private: /** @var Determine if object is initialized or not. */ bool m_initialized; + /** + * @brief Init Masstree memory pools. + * @return True if succeeded otherwise false. + * @note In case of failure it is the responsibility of the caller to call @ref DestroyPools(). + */ + bool InitPools() + { + m_leafsPool = + ObjAllocInterface::GetObjPool(m_index.getMemtagMaxSize(memtag_masstree_leaf), false, CACHE_LINE_SIZE); + if (!m_leafsPool) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create leaf pool"); + return false; // safe cleanup in DestroyPools() + } + + m_internodesPool = + ObjAllocInterface::GetObjPool(m_index.getMemtagMaxSize(memtag_masstree_internode), false, CACHE_LINE_SIZE); + if (!m_internodesPool) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create inter-node pool"); + return false; // safe cleanup in DestroyPools() + } + + int memtagMaxSize = m_index.getMemtagMaxSize(memtag_masstree_ksuffixes); + m_ksuffixSlab = new (std::nothrow) + SlabAllocator(SUFFIX_SLAB_MIN_BIN, static_cast(std::ceil(std::log2(memtagMaxSize))), false); + if (!m_ksuffixSlab) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create suffix allocator"); + return false; // safe cleanup in DestroyPools() + } + + if (!m_ksuffixSlab->IsSlabInitialized()) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Index", "Failed to create suffix allocator"); + return false; // safe cleanup in DestroyPools() + } + + return true; + } + + /** + * @brief Destroy Masstree memory pools. + */ + void DestroyPools() + { + if (m_leafsPool) { + ObjAllocInterface::FreeObjPool(&m_leafsPool); + m_leafsPool = NULL; + } + if (m_internodesPool) { + ObjAllocInterface::FreeObjPool(&m_internodesPool); + m_internodesPool = NULL; + } + + if (m_ksuffixSlab) { + delete m_ksuffixSlab; + m_ksuffixSlab = NULL; + } + } + /** * @brief Searches for a key in the index, returning an iterator to the closest matching key * according to the search criteria. diff --git a/src/gausskernel/storage/mot/core/storage/index/surrogate_key_manager.cpp b/src/gausskernel/storage/mot/core/storage/index/surrogate_key_manager.cpp index 6c5f55b7d..d8cffb66f 100644 --- a/src/gausskernel/storage/mot/core/storage/index/surrogate_key_manager.cpp +++ b/src/gausskernel/storage/mot/core/storage/index/surrogate_key_manager.cpp @@ -45,7 +45,7 @@ void SurrogateKeyManager::Destroy() void SurrogateKeyManager::PrintSurrogateMap() { for (int i = 0; i < (int)m_surrogateArray.size(); i++) { - MOT_LOG_INFO("ThreadID:%d InsertCounter = %lu\n", i, m_surrogateArray[i].GetCurrentCount()); + MOT_LOG_INFO("ThreadID: %d InsertCounter = %lu", i, m_surrogateArray[i].GetCurrentCount()); } } diff --git a/src/gausskernel/storage/mot/core/storage/key.h b/src/gausskernel/storage/mot/core/storage/key.h index cc1186eb0..098ca2cf9 100644 --- a/src/gausskernel/storage/mot/core/storage/key.h +++ b/src/gausskernel/storage/mot/core/storage/key.h @@ -25,8 +25,8 @@ #ifndef MOT_KEY_H #define MOT_KEY_H -#include -#include +#include +#include #include "utilities.h" #include "global.h" @@ -69,7 +69,7 @@ public: securec_check(erc, "\0", "\0"); } - inline __attribute__((always_inline)) virtual ~Key() + virtual ~Key() {} /** @@ -120,7 +120,7 @@ public: } /** - * @brief Fills a patern in a specific locaion of the key + * @brief Fills a pattern in a specific location of the key * to break similarity in secondary non-unique indices * @param buf Buffer to copy * @param len Length of the buffer @@ -167,14 +167,14 @@ public: CpKey(key.GetKeyBuf(), key.GetKeyLength()); } - void PrintKey() const + void PrintKey(const char* msg = nullptr) const { - MOT_LOG_INFO("Key:%s Key length = %d ", HexStr(m_keyBuf, m_keyLen).c_str(), m_keyLen); + MOT_LOG_INFO("(%s) Key:%s Key length = %d ", msg ? msg : "", HexStr(m_keyBuf, m_keyLen).c_str(), m_keyLen); } std::string GetKeyStr() const { - return HexStr(m_keyBuf, m_keyLen).c_str(); + return HexStr(m_keyBuf, m_keyLen); } bool operator==(const Key& key) const @@ -195,9 +195,11 @@ public: return ((memcmp(m_keyBuf, key.GetKeyBuf(), m_keyLen) <= 0) ? true : false); } - Key& operator=(const Key& other) + Key& operator=(const Key& right) { - CpKey(other); + if (this != &right) { + CpKey(right); + } return *this; } diff --git a/src/gausskernel/storage/mot/core/storage/row.cpp b/src/gausskernel/storage/mot/core/storage/row.cpp index 19bb027e4..102c519bd 100644 --- a/src/gausskernel/storage/mot/core/storage/row.cpp +++ b/src/gausskernel/storage/mot/core/storage/row.cpp @@ -31,22 +31,130 @@ namespace MOT { IMPLEMENT_CLASS_LOGGER(Row, Storage); -Row::Row(Table* hostTable) : m_rowHeader(), m_table(hostTable), m_rowId(INVALID_ROW_ID), m_keyType(KeyType::EMPTY_KEY) +Row::Row(Table* hostTable) + : m_rowCSN(INVALID_CSN), + m_next(nullptr), + m_table(hostTable), + m_rowId(INVALID_ROW_ID), + m_keyType(KeyType::EMPTY_KEY), + m_rowType(RowType::ROW) {} Row::Row(const Row& src) - : m_rowHeader(src.m_rowHeader), + : m_rowCSN(src.m_rowCSN), m_table(src.m_table), - m_surrogateKey(src.m_surrogateKey), m_pSentinel(src.m_pSentinel), m_rowId(src.m_rowId), m_keyType(src.m_keyType), - m_twoPhaseRecoverMode(src.m_twoPhaseRecoverMode) + m_rowType(src.m_rowType) + { errno_t erc = memcpy_s(this->m_data, this->GetTupleSize(), src.m_data, src.GetTupleSize()); securec_check(erc, "\0", "\0"); } +Row::Row(const Row& src, Table* tab) + : m_rowCSN(src.m_rowCSN), + m_table(tab), + m_pSentinel(src.m_pSentinel), + m_rowId(src.m_rowId), + m_keyType(src.m_keyType), + m_rowType(src.m_rowType) +{ + if (likely(tab == src.m_table)) { + errno_t erc = memcpy_s(this->m_data, this->GetTupleSize(), src.m_data, src.GetTupleSize()); + securec_check(erc, "\0", "\0"); + } else { + CopyVersion(src, tab->m_columns, tab->m_fieldCnt, src.m_table->m_columns, src.m_table->m_fieldCnt); + } +} + +void Row::CopyRowZero(const Row* src, Table* txnTable) +{ + CopyHeader(*src); + SetTable(txnTable); + CopyVersion( + *src, GetTable()->m_columns, GetTable()->m_fieldCnt, src->GetTable()->m_columns, src->GetTable()->m_fieldCnt); +} + +void Row::CopyHeader(const Row& src, RowType rowType) +{ + m_rowCSN = src.m_rowCSN; + m_table = src.m_table; + m_pSentinel = src.m_pSentinel; + m_rowId = src.m_rowId; + m_keyType = src.m_keyType; + m_rowType = rowType; +} +Row::Row(const Row& src, Column** newCols, uint32_t newColCnt, Column** oldCols, uint32_t oldColCnt) + : m_rowCSN(src.m_rowCSN), + m_next(src.m_next), + m_table(src.m_table), + m_pSentinel(src.m_pSentinel), + m_rowId(src.m_rowId), + m_keyType(src.m_keyType), + m_rowType(src.m_rowType) +{ + CopyVersion(src, newCols, newColCnt, oldCols, oldColCnt); +} + +Row::Row(const Row& src, bool nullBitsChanged, size_t srcBitSize, size_t newBitSize, size_t dataSize, Column* col) + : m_rowCSN(src.m_rowCSN), + m_next(src.m_next), + m_table(src.m_table), + m_pSentinel(src.m_pSentinel), + m_rowId(src.m_rowId), + m_keyType(src.m_keyType), + m_rowType(src.m_rowType) +{ + if (!nullBitsChanged) { + errno_t erc = memcpy_s(this->m_data, dataSize, src.m_data, dataSize); + securec_check(erc, "\0", "\0"); + } else { + errno_t erc = memcpy_s(m_data, newBitSize, src.m_data, srcBitSize); + securec_check(erc, "\0", "\0"); + erc = memcpy_s(m_data + newBitSize, dataSize - srcBitSize, src.m_data + srcBitSize, dataSize - srcBitSize); + securec_check(erc, "\0", "\0"); + } + if (col != nullptr) { + if (col->m_hasDefault) { + // pack from default value cannot fail + (void)col->Pack(m_data, col->m_defValue, col->m_defSize); + BITMAP_SET(m_data, col->m_id - 1); + } else { + BITMAP_CLEAR(m_data, col->m_id - 1); + } + } +} + +void Row::CopyVersion(const Row& src, Column** newCols, uint32_t newColCnt, Column** oldCols, uint32_t oldColCnt) +{ + const uint8_t* oldData = src.GetData(); + uint8_t* newBits = m_data + newCols[0]->m_offset; + uint32_t i = 0; + errno_t erc; + // copy old data + for (; i < oldColCnt; i++) { + if (!newCols[i]->m_isDropped) { + erc = memcpy_s( + m_data + newCols[i]->m_offset, newCols[i]->m_size, oldData + oldCols[i]->m_offset, oldCols[i]->m_size); + securec_check(erc, "\0", "\0"); + } else { + BITMAP_CLEAR(newBits, newCols[i]->m_id - 1); + } + } + // set new columns data + for (; i < newColCnt; i++) { + if (newCols[i]->m_hasDefault) { + // pack from default value cannot fail + (void)newCols[i]->Pack(m_data, newCols[i]->m_defValue, newCols[i]->m_defSize); + BITMAP_SET(newBits, newCols[i]->m_id - 1); + } else { + BITMAP_CLEAR(newBits, newCols[i]->m_id - 1); + } + } +} + void Row::SetValueVariable(int id, const void* ptr, uint32_t size) { const uint64_t fieldSize = m_table->GetFieldSize(id); @@ -60,13 +168,6 @@ void Row::CopyOpt(const Row* src) CopyData(src->GetData(), src->GetTupleSize()); } -RC Row::GetRow(AccessType type, TxnAccess* txn, Row* row, TransactionId& lastTid) const -{ - MOT_ASSERT(type != INS); - row->m_table = GetTable(); - return this->m_rowHeader.GetLocalCopy(txn, type, row, this, lastTid); -} - Row* Row::CreateCopy() { Row* row = m_table->CreateNewRow(); @@ -77,4 +178,19 @@ Row* Row::CreateCopy() } return row; } + +void Row::Print() +{ + Row* row = this; + while (row) { + MOT_LOG_INFO("%s: CSN = %lu rowID = %lu", + row->IsRowDeleted() ? "TOMBSTONE" : "ROW", + row->GetCommitSequenceNumber(), + row->GetRowId()); + row = row->GetNextVersion(); + MOT_LOG_INFO("|"); + MOT_LOG_INFO("V"); + } + MOT_LOG_INFO("NULL"); +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/row.h b/src/gausskernel/storage/mot/core/storage/row.h index 1302dedfd..12d3f3818 100644 --- a/src/gausskernel/storage/mot/core/storage/row.h +++ b/src/gausskernel/storage/mot/core/storage/row.h @@ -25,15 +25,15 @@ #ifndef MOT_ROW_H #define MOT_ROW_H -#include +#include #include #include #include #include "table.h" +#include "column.h" #include "key.h" #include "sentinel.h" -#include "row_header.h" #include "object_pool.h" // forward declaration @@ -42,6 +42,8 @@ class OccTransactionManager; class CheckpointWorkerPool; class RecoveryOps; +enum class RowType : uint8_t { ROW, TOMBSTONE, STABLE, ROW_ZERO }; + /** * @class Row * @brief The Row class holds all that is required to manage an in-memory row in @@ -57,10 +59,19 @@ public: Row(const Row& src); + Row(const Row& src, Table* tab); + Row(const Row& src, bool nullBitsChanged, size_t srcBitsSize, size_t newBitsSize, size_t dataSize, Column* col); + Row(const Row& src, Column** newCols, uint32_t newColCnt, Column** oldCols, uint32_t oldColCnt); + /** * @brief Destructor. */ - inline __attribute__((always_inline)) ~Row(){}; + inline __attribute__((always_inline)) ~Row() + { + m_table = nullptr; + m_pSentinel = nullptr; + m_next = nullptr; + }; // class non-copy-able, non-assignable, non-movable /** @cond EXCLUDE_DOC */ @@ -71,6 +82,9 @@ public: Row& operator=(Row&&) = delete; /** @endcond */ + void CopyRowZero(const Row* src, Table* txnTable); + + void CopyHeader(const Row& src, RowType rowType = RowType::ROW); /** * @brief Retrieves the table containing the row. * @return The owning table. @@ -80,6 +94,16 @@ public: return m_table; } + inline Table* GetOrigTable() const + { + return m_table->GetOrigTable(); + } + + inline void SetTable(Table* tab) + { + m_table = tab; + } + /** * @brief Sets the value of a field (column) in the row. * @param id The field id (ordinal number) in the row. @@ -155,8 +179,16 @@ public: */ inline void Copy(const Row* src) { - CopyData(src->GetData(), src->GetTupleSize()); - m_table = src->m_table; + if (unlikely(GetTable()->GetHasColumnChanges())) { + CopyVersion(*src, + GetTable()->m_columns, + GetTable()->m_fieldCnt, + src->GetTable()->m_columns, + src->GetTable()->m_fieldCnt); + } else { + CopyData(src->GetData(), src->GetTupleSize()); + m_table = src->m_table; + } } /** @@ -166,11 +198,7 @@ public: inline void DeepCopy(const Row* src) { CopyData(src->GetData(), src->GetTupleSize()); - SetCommitSequenceNumber(src->GetCommitSequenceNumber()); - m_table = src->m_table; - m_surrogateKey = src->m_surrogateKey; - m_rowId = src->m_rowId; - m_keyType = src->m_keyType; + CopyHeader(*src, src->GetRowType()); } /** @@ -180,6 +208,14 @@ public: */ void CopyOpt(const Row* src); + /** + * @brief full row copy, src row is an old version. + * @param src A source row. + * @param newCols A new data version + * @param oldCols An old data version + */ + void CopyVersion(const Row& src, Column** newCols, uint32_t newColCnt, Column** oldCols, uint32_t oldColCnt); + /** * @brief Retrieves a pointer to the raw data of the row. * @return The row data buffer. @@ -198,46 +234,34 @@ public: return m_table->GetTupleSize(); }; + RowType GetRowType() const + { + return m_rowType; + } + /** * @brief Sets (turns on) the absent bit for the row. */ - inline void SetAbsentRow() + inline void SetDeletedRow() { - m_rowHeader.SetAbsentBit(); + m_rowType = RowType::TOMBSTONE; } /** - * @brief Sets (turns on) the row header absent lock bit for the row. + * @brief Sets stable flag on the row. */ - inline void SetAbsentLockedRow() + inline void SetStableRow() { - m_rowHeader.SetAbsentLockedBit(); - } - - /** - * @brief Resets (turns off) the row header absent bit for the row. - */ - inline void UnsetAbsentRow() - { - m_rowHeader.UnsetAbsentBit(); + m_rowType = RowType::STABLE; } /** * @brief Queries the status of the row header absent bit for the row. * @return Boolean value denoting whether the absent bit is set or not. */ - inline bool IsAbsentRow() const - { - return m_rowHeader.IsAbsent(); - } - - /** - * @brief Queries the validity of the row header. - * @return Boolean value denoting whether the row is valid or not. - */ inline bool IsRowDeleted() const { - return m_rowHeader.IsRowDeleted(); + return (m_rowType == RowType::TOMBSTONE); } /** @@ -246,28 +270,18 @@ public: */ inline uint64_t GetCommitSequenceNumber() const { - return m_rowHeader.GetCSN(); + return m_rowCSN; } /** * @brief Sets the commit sequence number (CSN) of the row. - * @param csn The row commit sequnence number. + * @param csn The row commit sequence number. */ inline void SetCommitSequenceNumber(uint64_t csn) { - m_rowHeader.SetCSN(csn); + m_rowCSN = csn; } - /** - * @brief Accesses the row trough the concurrency contorl management. - * @param type The concurrency contorl access type. - * @param txn The local private transaction cache. - * @param[out] row Receives a copy of this row. - * @param[out] lastTid Receives the last tuple id version of the row. - * @return Return code denoting the execution result. - */ - RC GetRow(AccessType type, TxnAccess* txn, Row* row, TransactionId& lastTid) const; - /** * @brief Class specific in-place new operator. * @param size Object size in bytes. @@ -282,11 +296,12 @@ public: /** * @brief Class specific in-place delete operator. - * @param ptr The pointer to the object being deallocated. + * @param ptr The pointer to the object being de-allocated. * @param place The object's allocation address. */ static inline __attribute__((always_inline)) void operator delete(void* ptr, void* place) noexcept { + MOT_ASSERT(ptr != nullptr); (void)ptr; (void)place; } @@ -309,22 +324,36 @@ public: return m_pSentinel->GetStable(); }; - /** - * @brief Returns if row in a two phase recovery mode. - * @return Boolean value denoting whether the row is in a two phase recovery mode or not. - */ - bool GetTwoPhaseMode() + Row* GetNextVersion() const { - return m_twoPhaseRecoverMode; + return m_next; + } + + void SetNextVersion(Row* r) + { + m_next = r; } /** - * @brief Sets the row to two phase recovery mode - * @param val true/false + * @brief Obtains the original row's transaction id from the stable row. + * We reuse the m_next member in order to avoid adding more members. + * @return the transaction id. */ - void SetTwoPhaseMode(bool val) + uint64_t GetStableTid() const { - m_twoPhaseRecoverMode = val; + MOT_ASSERT(m_rowType == RowType::STABLE); + return reinterpret_cast(m_next); + } + + /** + * @brief Sets the original row's transaction id on the stable row. + * We reuse the m_next member in order to avoid adding more members. + * @param the transaction id. + */ + void SetStableTid(uint64_t tid) + { + MOT_ASSERT(m_rowType == RowType::STABLE); + m_next = reinterpret_cast(tid); } /** @@ -333,14 +362,14 @@ public: */ void SetPrimarySentinel(Sentinel* s) { - m_pSentinel = s; + m_pSentinel = reinterpret_cast(s); } /** * @brief Gets the row's primary sentinel. * @return pointer to the row's primary sentinel. */ - Sentinel* GetPrimarySentinel() + PrimarySentinel* GetPrimarySentinel() { return m_pSentinel; } @@ -389,7 +418,7 @@ public: */ uint8_t* GetSurrogateKeyBuff() const { - return (uint8_t*)&m_surrogateKey; + return (uint8_t*)&m_rowId; } /** @@ -422,9 +451,8 @@ public: * @brief Sets surrogate key. * @param key The surrogate key value. */ - void SetSurrogateKey(uint64_t key) + void SetSurrogateKey() { - m_surrogateKey = key; m_keyType = KeyType::SURROGATE_KEY; } @@ -451,7 +479,7 @@ public: */ uint64_t GetSurrogateKey() const { - return m_surrogateKey; + return htobe64(m_rowId); } /** @@ -469,7 +497,6 @@ public: */ void CopySurrogateKey(const Row* r) { - m_surrogateKey = r->GetSurrogateKey(); m_keyType = r->GetKeyType(); } @@ -494,25 +521,182 @@ public: } } + void Print(); + /** - * @brief a callback function to remove and destroy a row. - * @param gcParam1 A place holder for first paramater passed by the GC. - * @param gcParam1 A place holder for second param passed by the GC. - * @param dropIndex An indicator for drop index operator. + * @brief a callback function to remove and destroy a version chain. + * @param gcElement A place holder for the gc element metadata. + * @param oper GC operation for the current element + * @param aux a pointer to the GC delete vector + * @return size of cleaned element */ - static uint32_t RowDtor(void* gcParam1, void* gcParam2, bool dropIndex) + static uint32_t RowVersionDtor(void* gcElement, void* oper, void* aux) { - // We want to destroy the row even if this is a drop_index flow - uint32_t size = 0; - Row* r = reinterpret_cast(gcParam1); - // Add size of key and row - MOT_ASSERT(r != nullptr); - Table* t = r->GetTable(); - MOT_ASSERT(t != nullptr); - size += t->GetRowSizeFromPool(); - if (!dropIndex) { - t->DestroyRow(r); + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); + GcQueue::DeleteVector* deleteVector = static_cast(aux); + LimboElement* elem = reinterpret_cast(gcElement); + PrimarySentinel* ps = reinterpret_cast(elem->m_objectPool); + uint32_t size = ps->GetIndex()->GetTable()->GetRowSizeFromPool(); + if (unlikely(gcOperType == GC_OPERATION_TYPE::GC_OPER_DROP_INDEX)) { + return size; } + // We want to destroy the row even if this is a drop_index flow + GcSharedInfo& gcInfo = ps->GetGcInfo(); + MOT_ASSERT(gcInfo.GetCounter() > 0); + // Decrement reference count + uint32_t gcSlot = gcInfo.RefCountUpdate(DEC); + gcSlot--; + // Check if we are the last owners of the sentinel + // if so reclaim it + if ((gcSlot == 0) and ps->IsSentinelRemovable()) { + MOT_LOG_DEBUG("RowVersionDtor Deleting ps %p minActiveCSN %lu ", ps, MOT::g_gcActiveEpoch); + ps->ReclaimSentinel(ps); + return size; + } + uint64_t reclaimCSN = elem->m_csn; + // If row was reclaimed skip this element + if (reclaimCSN <= gcInfo.GetMinCSN()) { + return size; + } + // We try to reclaim every N element + if (gcSlot and gcSlot % GC_MAX_ELEMENTS_TO_SKIP) { + // At this point we can check if the row was updated + // If so we can skip + if (ps->GetData() != elem->m_objectPtr) { + return size; + } + } + bool res = gcInfo.TryLock(); + if (res == false) { + return size; + } + uint64_t min_csn = gcInfo.GetMinCSN(); + // Locked Re-verify element is still valid + if (reclaimCSN <= min_csn) { + gcInfo.Release(); + return size; + } + // Set MIN_CSN in the gcInfo to inform Concurrent GC's about the latest version. + gcInfo.SetMinCSN(reclaimCSN); + Row* tmp = nullptr; + Row* versionRow = reinterpret_cast(elem->m_objectPtr); + // Add size of key and row + MOT_ASSERT(versionRow != nullptr); + Table* t = versionRow->GetTable(); + MOT_ASSERT(t != nullptr); + + // We reclaim all the versions below this versions + tmp = versionRow; + versionRow = versionRow->GetNextVersion(); + // Detach the list of rows + tmp->SetNextVersion(nullptr); + + // Clean older version + while (versionRow) { + tmp = versionRow; + versionRow = versionRow->GetNextVersion(); + if (tmp->IsRowDeleted() == false) { + t->DestroyRow(tmp); + } else { + uint64_t tombstoneCSN = tmp->GetCommitSequenceNumber(); + if (tombstoneCSN > min_csn) { + (void)t->GCRemoveRow(deleteVector, tmp, gcOperType); + } + t->DestroyRow(tmp); + } + } + // Unlock gcInfo + gcInfo.Release(); + + return size; + } + + /** + * @brief a callback function to remove and destroy a deleted version chain. + * @param gcElement A place holder for the gc element metadata. + * @param oper GC operation for the current element + * @param aux a pointer to the GC delete vector + * @return size of cleaned element + */ + static uint32_t DeleteRowDtor(void* gcElement, void* oper, void* aux) + { + GC_OPERATION_TYPE gcOperType = (*(GC_OPERATION_TYPE*)oper); + GcQueue::DeleteVector* deleteVector = static_cast(aux); + LimboElement* limboElem = reinterpret_cast(gcElement); + PrimarySentinel* ps = reinterpret_cast(limboElem->m_objectPool); + uint32_t size = ps->GetIndex()->GetTable()->GetRowSizeFromPool(); + if (unlikely(gcOperType == GC_OPERATION_TYPE::GC_OPER_DROP_INDEX)) { + return size; + } + // We want to destroy the row even if this is a drop_index flow + GcSharedInfo& gcInfo = ps->GetGcInfo(); + MOT_ASSERT(gcInfo.GetCounter() > 0); + // Decrement reference count + uint32_t gcSlot = gcInfo.RefCountUpdate(DEC); + // Check if we are the last owners of the sentinel + // if so reclaim it + if ((gcSlot == 1) and ps->IsSentinelRemovable()) { + MOT_LOG_DEBUG("RowVersionDtor Deleting ps %p minActiveCSN %lu ", ps, MOT::g_gcActiveEpoch); + ps->ReclaimSentinel(ps); + return size; + } + uint64_t reclaimCSN = limboElem->m_csn; + // If row was reclaimed skip this element + if (reclaimCSN <= gcInfo.GetMinCSN()) { + MOT_LOG_DEBUG("Skipping Element %s %d ", __func__, __LINE__); + return size; + } + // Lock the gcInfo + gcInfo.Lock(); + uint64_t min_csn = gcInfo.GetMinCSN(); + // Re-verify element is still valid + if (reclaimCSN <= min_csn) { + gcInfo.Release(); + return size; + } + // If a key was inserted on top of us ignore this element + // Since we passed CSN check + // Serialize the operations from newest to oldest! + if (ps->GetData() != limboElem->m_objectPtr) { + MOT_LOG_DEBUG("Skipping Element %s %d ", __func__, __LINE__); + gcInfo.Release(); + return size; + } + gcInfo.SetMinCSN(reclaimCSN); + Row* tombstone = reinterpret_cast(limboElem->m_objectPtr); + Row* versionRow = tombstone->GetNextVersion(); + // Add size of key and row + MOT_ASSERT(tombstone != nullptr); + Table* table = tombstone->GetTable(); + MOT_ASSERT(table != nullptr); + (void)table->GCRemoveRow(deleteVector, tombstone, gcOperType); + + if (versionRow) { + Row* currentRow = versionRow; + versionRow = versionRow->GetNextVersion(); + currentRow->SetNextVersion(nullptr); + } + + // Clean older version + while (versionRow) { + Row* tmp = versionRow; + versionRow = versionRow->GetNextVersion(); + if (tmp->IsRowDeleted() == false) { + MOT_LOG_DEBUG("Deleting Version %s %d ", __func__, __LINE__); + table->DestroyRow(tmp); + } else { + MOT_LOG_DEBUG("Deleting tombstone - Clean Secondary Only! %s %d ", __func__, __LINE__); + uint64_t tombstoneCSN = tmp->GetCommitSequenceNumber(); + if (tombstoneCSN > min_csn) { + (void)table->GCRemoveRow(deleteVector, tmp, gcOperType); + } + table->DestroyRow(tmp); + } + } + + // Unlock gcInfo + gcInfo.Release(); + return size; } @@ -526,7 +710,7 @@ private: * @param dataSize[out] The number of bytes to update in the field (column). * @return The field offset in the row. */ - inline uint64_t GetSetValuePosition(int id, uint64_t& dataSize) + inline uint64_t GetSetValuePosition(int id, uint64_t& dataSize) const { uint64_t dSize = m_table->GetFieldSize(id); uint64_t pos = m_table->GetFieldOffset(id); @@ -563,19 +747,17 @@ protected: void SetValue(int colId, const void* value) = delete; /** @endcond */ - // Header of the row reserved for the concurrency control method - // NOTE: This member should always be first (DO NOT CHANGE) /** @var The row header. */ - RowHeader m_rowHeader; + uint64_t m_rowCSN; + + /** @var The next version ordered from N2O */ + Row* m_next = nullptr; /** @var The table to which this row belongs. */ Table* m_table = nullptr; - /** @var The internal key number on cases where primary key doesn't exist. */ - uint64_t m_surrogateKey; - /** @var The reference to the sentinel that points to this row. */ - Sentinel* m_pSentinel = nullptr; + PrimarySentinel* m_pSentinel = nullptr; /** @var the row id. */ uint64_t m_rowId; @@ -583,8 +765,8 @@ protected: /** @var The key type. */ KeyType m_keyType; - /** @var A flag to identify if row is in recover mode state. */ - bool m_twoPhaseRecoverMode = false; + /** @var The row type. */ + RowType m_rowType; /** @var The raw buffer holding the row data. Starts at the end of the class * Must be last member */ @@ -610,6 +792,7 @@ protected: friend Index; friend RecoveryOps; friend Table; + friend TxnTable; DECLARE_CLASS_LOGGER() }; diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/CMakeLists.txt b/src/gausskernel/storage/mot/core/storage/sentinel/CMakeLists.txt new file mode 100644 index 000000000..97cf3cf29 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/CMakeLists.txt @@ -0,0 +1,12 @@ +#This is the main CMAKE for building MOT core/storage/sentinel component. + +AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} TGT_mot_core_storage_sentinel_SRC) + +set(TGT_mot_core_storage_sentinel_INC + ${PROJECT_SRC_DIR}/include + ${MOT_CORE_INCLUDE_PATH} +) + +add_static_objtarget(gausskernel_storage_mot_core_storage_sentinel TGT_mot_core_storage_sentinel_SRC TGT_mot_core_storage_sentinel_INC + "${mot_core_DEF_OPTIONS}" "${mot_core_COMPILE_OPTIONS}" "${mot_core_LINK_OPTIONS}") + diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/gc_shared_info.h b/src/gausskernel/storage/mot/core/storage/sentinel/gc_shared_info.h new file mode 100644 index 000000000..e8f0edcb6 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/gc_shared_info.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * gc_shared_info.h + * GC concurrent reclaim structure. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/gc_shared_info.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef GC_SHARED_INFO_H +#define GC_SHARED_INFO_H + +#include "cycles.h" +#include "mot_atomic_ops.h" +#include "debug_utils.h" +#include "mm_def.h" +#include + +namespace MOT { + +enum GcInfoFlags : uint64_t { + GC_INFO_LOCK_BIT = 1UL << 63, + GC_INFO_CSN_SIZE = 63, + GC_INFO_CSN_MASK = (1UL << GC_INFO_CSN_SIZE) - 1, + GC_INFO_COUNTER_SIZE = 31, + GC_INFO_COUNTER_LOCK_BIT = 1UL << 31, + GC_INFO_COUNTER_MASK = ((1UL << GC_INFO_COUNTER_SIZE) - 1), +}; + +/** + * @class GcSharedInfo + * @brief GC descriptor for primary sentinel + */ + +class GcSharedInfo { +public: + inline uint64_t GetMinCSN() const + { + return m_minCsn & GC_INFO_CSN_MASK; + } + + void SetMinCSN(uint64_t min_csn) + { + m_minCsn = (m_minCsn & GC_INFO_LOCK_BIT) | min_csn; + } + + inline uint32_t GetCounter() const + { + return (m_refCount.load(std::memory_order_relaxed)); + } + + void Lock() + { + uint64_t v = m_minCsn; + while ((v & GC_INFO_LOCK_BIT) || !__sync_bool_compare_and_swap(&m_minCsn, v, v | GC_INFO_LOCK_BIT)) { + PAUSE + v = m_minCsn; + } + } + + inline void Release() + { + MOT_ASSERT(m_minCsn & GC_INFO_LOCK_BIT); + +#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) + m_minCsn = m_minCsn & (~GC_INFO_LOCK_BIT); +#else + + uint64_t v = m_minCsn; + while (!__sync_bool_compare_and_swap(&m_minCsn, v, (v & ~GC_INFO_LOCK_BIT))) { + PAUSE + v = m_minCsn; + } +#endif + } + + bool TryLock() + { + uint64_t v = m_minCsn; + if (v & GC_INFO_LOCK_BIT) { // already locked + return false; + } + return __sync_bool_compare_and_swap(&m_minCsn, v, (v | GC_INFO_LOCK_BIT)); + } + + uint32_t RefCountUpdate(AccessType type) + { + if (type == INC) { + return m_refCount.fetch_add(1, std::memory_order_relaxed); + } else { + MOT_ASSERT(GetCounter() != 0); + return m_refCount.fetch_sub(1, std::memory_order_relaxed); + } + } + +private: + /** @var m_minCsn The minimal CSN where this version was reclaimed */ + uint64_t m_minCsn = 0; + + /** @var m_refCount Reference count for the current reclaimed version */ + std::atomic m_refCount{0}; +}; + +} // namespace MOT + +#endif // GC_SHARED_INFO_H diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.cpp b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.cpp new file mode 100644 index 000000000..b92735fde --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * primary_sentinel.cpp + * Primary Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/primary_sentinel.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "sentinel.h" +#include "row.h" + +namespace MOT { + +IMPLEMENT_CLASS_LOGGER(PrimarySentinel, Storage); + +void PrimarySentinel::SetStable(Row* const& row) +{ + m_stable = (m_stable & ~S_OBJ_ADDRESS_MASK) | ((uint64_t)(row)&S_OBJ_ADDRESS_MASK); + if (row != nullptr) { + row->SetStableRow(); + } +} + +Row* PrimarySentinel::GetStable() const +{ + return reinterpret_cast((uint64_t)(m_stable & S_OBJ_ADDRESS_MASK)); +}; + +bool PrimarySentinel::GetStableStatus() const +{ + return (m_stable & STABLE_BIT) == STABLE_BIT; +} + +void PrimarySentinel::SetStableStatus(bool val) +{ + if (val == true) { + m_stable |= STABLE_BIT; + } else { + m_stable &= ~STABLE_BIT; + } +} + +Row* PrimarySentinel::GetVisibleRowVersion(uint64_t csn) +{ + // 1.Need to retrieve a valid snapshot + // 2.First we take a snapshot of the current top version + // 3.To return a valid snapshot we need to guaranty that at least one commit cycle as passed. + // 4. Either by getting a more updated row or a lock cycle is finished(If we started in a lock state) + Row* topRow = GetData(); + bool isUncommittedLocked = false; + bool isLongLock = false; + uint32_t waitCount = 0; + + while (!IsCommited() and IsLocked()) { + CpuCyclesLevelTime::Sleep(5); + isUncommittedLocked = true; + } + + // If the sentinel is locked and uncommitted and we exited the loop + // We finished a commit cycle - sentinel is committed + if (isUncommittedLocked) { + return GetRowFromChain(csn, GetData()); + } + + while (IsLocked()) { + Row* r = GetData(); + if (r != nullptr) { + if (r != topRow or (csn < r->GetCommitSequenceNumber())) { + // Finished cycle + return GetRowFromChain(csn, GetData()); + } + } + if (!isLongLock) { + CpuCyclesLevelTime::Sleep(5); + waitCount++; + if (waitCount == SPIN_WAIT_COUNT) { + isLongLock = true; + } + } else { + std::this_thread::sleep_for(chrono::microseconds(100)); + } + } + + // At this point our snapshot origin is guarantied to be after commit + return GetRowFromChain(csn, GetData()); +} + +Row* PrimarySentinel::GetRowFromChain(uint64_t csn, Row* row) +{ + while (row) { + if (csn > row->GetCommitSequenceNumber()) { + return row; + } + row = row->GetNextVersion(); + } + return nullptr; +} + +Row* PrimarySentinel::GetVisibleRowForUpdate(uint64_t csn, ISOLATION_LEVEL isolationLevel) +{ + bool isUncommittedLocked = false; + Row* topRow = GetData(); + + // First check if the key is valid! + if (IsCommited()) { + if (csn <= m_startCSN) { + // Phantom insert + return nullptr; + } + } + + // If Sentinel is not committed and locked - spin + while (IsLockedAndDirty() == true) { + CpuCyclesLevelTime::Sleep(5); + isUncommittedLocked = true; + } + + /* If the sentinel is locked and uncommitted and we exited the loop + * We finished a commit cycle - sentinel is committed UP/DEL over new INSERT + */ + if (isUncommittedLocked) { + // Either Sentinel is committed or txn is aborted - sentinel is dirty + topRow = GetData(); + return topRow; + } + + // We can reach this point if Sentinel is committed or dirty + if (isolationLevel == READ_COMMITED) { + while (IsLocked()) { + Row* r = GetData(); + if (r != nullptr) { + if (r != topRow or IsWriteBitOn() == true) { + return r; + } + } + } + } else { + MOT_ASSERT(isolationLevel == REPEATABLE_READ); + while (IsLocked()) { + Row* r = GetData(); + if (r != nullptr) { + if (csn <= r->GetCommitSequenceNumber()) { + return r; + } + if (r != topRow) { + return GetData(); + } + } + } + } + if (IsDirty() == true) { + return nullptr; + } + + return GetData(); +} + +PrimarySentinel* PrimarySentinel::GetVisiblePrimaryHeader(uint64_t csn) +{ + // If Sentinel is locked and uncommitted wait for commit to end + while (IsLockedAndDirty()) { + CpuCyclesLevelTime::Sleep(5); + } + /* At this point Sentinel is either committed/aborted in + case it is committed we can return a valid Sentinel. For + a committed Sentinel we need further examination for + deleted sentinel. */ + if (IsCommited()) { + if (csn > m_startCSN) { + return this; + } else { + // Phantom insert + return nullptr; + } + } + return nullptr; +} + +void PrimarySentinel::Print() +{ + MOT_LOG_INFO("PrimarySentinel: Index name: %s startCSN = %lu indexOrder = PRIMARY_INDEX TID=%lu", + GetIndex()->GetName().c_str(), + GetStartCSN(), + GetTransactionId()); + MOT_LOG_INFO("|"); + MOT_LOG_INFO("V"); + + if (IsCommited()) { + Row* row = GetData(); + row->Print(); + } else { + MOT_LOG_INFO("NULL"); + } + MOT_LOG_INFO("---------------------------------------------------------------"); +} + +void PrimarySentinel::ReclaimSentinel(Sentinel* s) +{ + MOT_ASSERT(s != nullptr); + Row* row = s->GetData(); + Index* index = s->GetIndex(); + Table* t = index->GetTable(); + index->SentinelRelease(s); + while (row) { + Row* tmp = row; + row = row->GetNextVersion(); + t->DestroyRow(tmp); + } +} + +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.h b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.h new file mode 100644 index 000000000..c31ceab91 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * primary_sentinel.h + * Primary Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/primary_sentinel.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef PRIMARY_SENTINEL_H +#define PRIMARY_SENTINEL_H + +#include "sentinel.h" + +namespace MOT { +class PrimarySentinel : public Sentinel { +public: + ~PrimarySentinel() override + {} + + static constexpr uint16_t SPIN_WAIT_COUNT = 16; + + bool GetStableStatus() const override; + + void SetStableStatus(bool val) override; + + /** + * @brief Sets the current row as stable for CALC + */ + void SetStable(Row* const& row) override; + + /** + * @brief Retrieves the stable row + * @return The row object. + */ + Row* GetStable() const override; + + Row* GetData(void) const override + { + return static_cast((void*)(GetStatus() & S_OBJ_ADDRESS_MASK)); + } + + bool IsSentinelRemovable() const + { + return ((GetCounter() == 0) and (m_gcInfo.GetCounter() == 0)); + } + + /** + * @brief Retrieves the row for the current CSN + * @param csn the current snapshot + * @return The sentinel data. + */ + Row* GetVisibleRowVersion(uint64_t csn) override; + + /** + * @brief Retrieves the primary sentinel for the current CSN + * @param csn the current snapshot + * @return The primary sentinel. + */ + PrimarySentinel* GetVisiblePrimaryHeader(uint64_t csn) override; + + /** + * @brief Retrieves the primary sentinel associated with the current sentinel + * @return void pointer to the primary sentinel. + */ + void* GetPrimarySentinel() const override + { + return static_cast(const_cast(this)); + } + + inline bool GetStablePreAllocStatus() const + { + return (m_stable & PRE_ALLOC_BIT) == PRE_ALLOC_BIT; + } + + inline void SetStablePreAllocStatus(bool val) + { + if (val) { + m_stable |= PRE_ALLOC_BIT; + } else { + m_stable &= ~PRE_ALLOC_BIT; + } + } + + /** + * @brief Retrieves the latest row which CSN is >= csn + * @param csn the current snapshot + * @return The sentinel data. + */ + Row* GetVisibleRowForUpdate(uint64_t csn, ISOLATION_LEVEL isolationLevel); + + Row* GetRowFromChain(uint64_t csn, Row* row); + + void Print() override; + + void ReclaimSentinel(Sentinel* s); + + GcSharedInfo& GetGcInfo() + { + return m_gcInfo; + } + + inline void SetTransactionId(uint64_t tid) + { + m_transactionId = tid; + } + + uint64_t GetTransactionId() const + { + return m_transactionId; + } + +private: + /** @var m_stable A Container for the row and its status bit. */ + volatile uint64_t m_stable = 0; + + /** @var m_transactionId The latest tid committed on this tuple (recovery). */ + volatile uint64_t m_transactionId = 0; + + /** @var m_gcInfo Shared synchronization container for garbage collection. */ + GcSharedInfo m_gcInfo; + + DECLARE_CLASS_LOGGER() +}; + +} // namespace MOT + +#endif // PRIMARY_SENTINEL_H diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel_node.h b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel_node.h new file mode 100644 index 000000000..06da78eb7 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/primary_sentinel_node.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * primary_sentinel_node.h + * Primary Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/primary_sentinel_node.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef PRIMARY_SENTINEL_NODE_H +#define PRIMARY_SENTINEL_NODE_H + +namespace MOT { + +class PrimarySentinel; + +/** + * @class PrimarySentinelNode + * @brief Node specifying the current primary sentinel the secondary-unique sentinel points to. + */ + +class PrimarySentinelNode { +public: + PrimarySentinelNode() : m_startCSN(0), m_endCSN(NODE_INIT_CSN), m_primarySentinel(nullptr), m_next(nullptr) + {} + + void Init(uint64_t start_csn, uint64_t end_csn, PrimarySentinel* p, PrimarySentinelNode* next = nullptr) + { + m_startCSN = start_csn; + m_endCSN = end_csn; + m_primarySentinel = p; + m_next = next; + } + + void SetStartCSN(uint64_t start_csn) + { + m_startCSN = start_csn; + } + + void SetEndCSN(uint64_t end_csn) + { + m_endCSN = end_csn; + } + + void SetPrimarySentinel(PrimarySentinel* ps) + { + m_primarySentinel = ps; + } + + void SetNextVersion(PrimarySentinelNode* v) + { + m_next = v; + } + + uint64_t GetStartCSN() const + { + return m_startCSN; + } + + uint64_t GetEndCSN() const + { + return m_endCSN; + } + + std::string GetEndCSNStr() const + { + if (m_endCSN == NODE_INIT_CSN) { + return std::string("INF"); + } else { + return std::to_string(m_endCSN); + } + } + + PrimarySentinel* GetPrimarySentinel() const + { + return m_primarySentinel; + } + + PrimarySentinelNode* GetNextVersion() const + { + return m_next; + } + + inline bool IsNodeDeleted() const + { + return m_endCSN != NODE_INIT_CSN; + } + + static constexpr uint64_t NODE_INIT_CSN = static_cast(-1); + +private: + /** @var The Start CSN of the sentinel. */ + uint64_t m_startCSN; + + /** @var The End CSN of the sentinel. */ + uint64_t m_endCSN; + + /** @var Pointer to the primary sentinel */ + PrimarySentinel* m_primarySentinel; + + /** @var Pointer to the next node */ + PrimarySentinelNode* m_next; +}; + +} // namespace MOT + +#endif // PRIMARY_SENTINEL_NODE_H diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.cpp b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.cpp new file mode 100644 index 000000000..145620107 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * secondary_sentinel.cpp + * Secondary Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/secondary_sentinel.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "secondary_sentinel.h" +#include "row.h" + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(SecondarySentinel, Storage); + +void SecondarySentinel::Print() +{ + MOT_LOG_INFO("---------------------------------------------------------------"); + MOT_LOG_INFO("SecondarySentinel: Index name: %s" + " startCSN = %lu endCSN = %s indexOrder = SECONDARY_NON_UNIQUE_INDEX", + GetIndex()->GetName().c_str(), + GetStartCSN(), + GetEndCSNStr().c_str()); + MOT_LOG_INFO("|"); + MOT_LOG_INFO("V"); + + if (IsCommited()) { + PrimarySentinel* ps = reinterpret_cast(GetPrimarySentinel()); + return ps->Print(); + } else { + MOT_LOG_INFO("NULL"); + } + MOT_LOG_INFO("---------------------------------------------------------------"); +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.h b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.h new file mode 100644 index 000000000..521e1c18f --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * secondary_sentinel.h + * Secondary Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/secondary_sentinel.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef SECONDARY_SENTINEL_H +#define SECONDARY_SENTINEL_H + +#include "sentinel.h" +#include "primary_sentinel.h" + +namespace MOT { +class SecondarySentinel : public Sentinel { +public: + ~SecondarySentinel() override + {} + + uint64_t GetEndCSN() const + { + return m_endCSN; + } + + std::string GetEndCSNStr() const + { + if (m_endCSN == SENTINEL_INIT_CSN) { + return std::string("INF"); + } else { + return std::to_string(m_endCSN); + } + } + + void SetEndCSN(uint64_t endCSN) override + { + m_endCSN = endCSN; + } + + /** + * @brief Retrieves the data associated with the sentinel index entry. + * @return The sentinel data. + */ + Row* GetData(void) const override + { + PrimarySentinel* ps = reinterpret_cast((uint64_t)(GetStatus() & S_OBJ_ADDRESS_MASK)); + if (ps != nullptr) { + return ps->GetData(); + } else { + return nullptr; + } + } + + /** + * @brief Retrieves the row for the current CSN + * @param csn the current snapshot + * @return The sentinel data. + */ + Row* GetVisibleRowVersion(uint64_t csn) override + { + while (IsLocked()) { + CpuCyclesLevelTime::Sleep(1); + } + if (IsCommited()) { + // Valid range (start_csn,end_csn] + if (csn <= m_endCSN and csn > m_startCSN) { + PrimarySentinel* ps = reinterpret_cast(GetPrimarySentinel()); + MOT_ASSERT(ps != nullptr); + return ps->GetVisibleRowVersion(csn); + } + } + return nullptr; + } + + /** + * @brief Retrieves the Correct primary-sentinel for the current CSN + * @param csn the current snapshot + * @return The primary-sentinel. + */ + PrimarySentinel* GetVisiblePrimaryHeader(uint64_t csn) override + { + while (!IsCommited() and IsLocked()) { + CpuCyclesLevelTime::Sleep(1); + } + if (IsCommited()) { + if (csn <= m_endCSN and csn > m_startCSN) { + PrimarySentinel* ps = reinterpret_cast(GetPrimarySentinel()); + MOT_ASSERT(ps != nullptr); + return ps->GetVisiblePrimaryHeader(csn); + } + } + return nullptr; + } + + /** + * @brief Retrieves the primary sentinel associated with the current sentinel + * @return void pointer to the primary sentinel. + */ + void* GetPrimarySentinel() const override + { + return reinterpret_cast((uint64_t)(GetStatus() & S_OBJ_ADDRESS_MASK)); + } + + void Print() override; + +private: + /** @var m_endCSN indicate the timestamp the sentinel was deleted */ + uint64_t m_endCSN = SENTINEL_INIT_CSN; + + DECLARE_CLASS_LOGGER() +}; +} // namespace MOT + +#endif // SECONDARY_SENTINEL_H diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.cpp b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.cpp new file mode 100644 index 000000000..3a3ed93e6 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * secondary_sentinel_unique.cpp + * Secondary Unique Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/secondary_sentinel_unique.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "secondary_sentinel_unique.h" +#include "row.h" + +namespace MOT { + +class Index; + +IMPLEMENT_CLASS_LOGGER(SecondarySentinelUnique, Storage); + +PrimarySentinel* SecondarySentinelUnique::GetVisiblePrimaryHeader(uint64_t csn) +{ + PrimarySentinelNode* node = nullptr; + while (!IsCommited() and IsLocked()) { + CpuCyclesLevelTime::Sleep(1); + } + if (IsCommited()) { + if (csn > m_startCSN) { + node = GetTopNode(); + } else { + // Phantom insert + return nullptr; + } + MOT_ASSERT(node != nullptr); + while (node) { + // Check if key is deleted + // Valid range (start_csn,end_csn] + if (csn <= node->GetEndCSN() and csn > node->GetStartCSN()) { + return node->GetPrimarySentinel(); + } + node = node->GetNextVersion(); + } + } + return nullptr; +} + +PrimarySentinelNode* SecondarySentinelUnique::GetNodeByCSN(uint64_t csn) +{ + PrimarySentinelNode* node = nullptr; + while (IsLocked()) { + CpuCyclesLevelTime::Sleep(1); + } + if (IsCommited()) { + if (csn > m_startCSN) { + node = GetTopNode(); + } else { + // Phantom insert + return nullptr; + } + while (node) { + // Check if key is deleted + // Valid range (start_csn,end_csn] + if (csn > node->GetEndCSN()) { + return nullptr; + } else { + if (csn > node->GetStartCSN()) { + return node; + } + } + node = node->GetNextVersion(); + } + } + return nullptr; +} + +void SecondarySentinelUnique::ReleaseAllNodes() +{ + PrimarySentinelNode* node = GetTopNode(); + Index* index = GetIndex(); + SetNextPtr(nullptr); + ReleaseNodeChain(node, index); +} + +void SecondarySentinelUnique::DetachUnusedNodes(uint64_t snapshot, PrimarySentinelNode*& detachedChain) +{ + detachedChain = nullptr; + PrimarySentinelNode* node = GetTopNode(); + while (node) { + PrimarySentinelNode* tmp = node->GetNextVersion(); + if (tmp and snapshot >= tmp->GetEndCSN()) { + node->SetNextVersion(nullptr); + detachedChain = tmp; + return; + } + node = tmp; + } +} + +void SecondarySentinelUnique::Print() +{ + MOT_LOG_INFO("---------------------------------------------------------------"); + MOT_LOG_INFO("SecondarySentinel: Index name: %s startCSN = %lu indexOrder = SECONDARY_UNIQUE_INDEX", + GetIndex()->GetName().c_str(), + GetStartCSN()); + MOT_LOG_INFO("|"); + MOT_LOG_INFO("V"); + + if (IsCommited()) { + PrimarySentinelNode* node = GetTopNode(); + while (node) { + MOT_LOG_INFO("SecondaryUniqueNode: startCSN = %lu endCSN = %s nextNode = %p", + node->GetStartCSN(), + node->GetEndCSNStr().c_str(), + node->GetNextVersion()); + MOT_LOG_INFO("|"); + MOT_LOG_INFO("V"); + node->GetPrimarySentinel()->Print(); + node = node->GetNextVersion(); + } + } else { + MOT_LOG_INFO("NULL"); + } + MOT_LOG_INFO("---------------------------------------------------------------"); +} + +void SecondarySentinelUnique::ReleaseNodeChain(PrimarySentinelNode* node, Index* index) +{ + MOT_ASSERT(index != nullptr); + while (node) { + PrimarySentinelNode* tmp = node->GetNextVersion(); + index->SentinelNodeRelease(node); + node = tmp; + } +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.h b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.h new file mode 100644 index 000000000..6f643d5b0 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/sentinel/secondary_sentinel_unique.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * secondary_sentinel_unique.h + * Secondary Unique Index Sentinel + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/secondary_sentinel_unique.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef SECONDARY_SENTINEL_UNIQUE_H +#define SECONDARY_SENTINEL_UNIQUE_H + +#include "sentinel.h" +#include "primary_sentinel.h" + +namespace MOT { +/** + * @class SecondarySentinelUnique + * @brief Secondary unique index sentinel. + */ +class SecondarySentinelUnique : public Sentinel { +public: + ~SecondarySentinelUnique() override + {} + + inline PrimarySentinelNode* GetTopNode() const + { + return reinterpret_cast((uint64_t)(GetStatus() & S_OBJ_ADDRESS_MASK)); + } + + void SetEndCSN(uint64_t endCSN) override + { + PrimarySentinelNode* node = GetTopNode(); + MOT_ASSERT(node != nullptr); + node->SetEndCSN(endCSN); + } + + /** + * @brief Retrieves the row for the current CSN + * @param csn the current snapshot + * @return The sentinel data. + */ + Row* GetVisibleRowVersion(uint64_t csn) override + { + while (IsLocked()) { + CpuCyclesLevelTime::Sleep(1); + } + if (IsCommited()) { + PrimarySentinel* ps = GetVisiblePrimaryHeader(csn); + if (!ps) { + return nullptr; + } + return ps->GetVisibleRowVersion(csn); + } + return nullptr; + } + + /** + * @brief Retrieves the Correct primary-sentinel for the current CSN + * @param csn the current snapshot + * @return The primary-sentinel. + */ + PrimarySentinel* GetVisiblePrimaryHeader(uint64_t csn) override; + + /** + * @brief Retrieves the Correct primary-sentinel node for the current CSN + * @param csn the current snapshot + * @return The primary-sentinel node. + */ + PrimarySentinelNode* GetNodeByCSN(uint64_t csn); + + /** + * @brief Return all nodes to the index memory pool + */ + void ReleaseAllNodes(); + + void DetachUnusedNodes(uint64_t snapshot, PrimarySentinelNode*& detachedChain); + + void Print() override; + + static void ReleaseNodeChain(PrimarySentinelNode* node, Index* index); + +private: + DECLARE_CLASS_LOGGER() +}; + +} // namespace MOT + +#endif // SECONDARY_SENTINEL_UNIQUE_H diff --git a/src/gausskernel/storage/mot/core/storage/sentinel.cpp b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.cpp similarity index 70% rename from src/gausskernel/storage/mot/core/storage/sentinel.cpp rename to src/gausskernel/storage/mot/core/storage/sentinel/sentinel.cpp index 79c82549e..5ad3b75b8 100644 --- a/src/gausskernel/storage/mot/core/storage/sentinel.cpp +++ b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.cpp @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * sentinel.cpp - * Primary/Secondary index sentinel. + * Base Index Sentinel * * IDENTIFICATION * src/gausskernel/storage/mot/core/storage/sentinel.cpp @@ -26,15 +26,24 @@ #include "row.h" namespace MOT { -RC Sentinel::RefCountUpdate(AccessType type, uint64_t tid) + +IMPLEMENT_CLASS_LOGGER(Sentinel, Storage); + +RC Sentinel::RefCountUpdate(AccessType type) +{ + LockRefCount(); + RC rc = RefCountUpdateNoLock(type); + UnlockRefCount(); + return rc; +} + +RC Sentinel::RefCountUpdateNoLock(AccessType type) { RC rc = RC_OK; - LockRefCount(); if (type == INC) { if (GetCounter() == 0) { - ReleaseRefCount(); - return RC::RC_INDEX_RETRY_INSERT; + rc = RC::RC_INDEX_RETRY_INSERT; } else { IncCounter(); } @@ -45,8 +54,21 @@ RC Sentinel::RefCountUpdate(AccessType type, uint64_t tid) rc = RC::RC_INDEX_DELETE; } } - ReleaseRefCount(); return rc; } + +RC Sentinel::RollBackUnique() +{ + RC rc = RC_OK; + LockRefCount(); + DecCounter(); + if (GetCounter() == 0) { + IncCounter(); + rc = RC::RC_INDEX_DELETE; + } + + UnlockRefCount(); + return rc; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/sentinel.h b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h similarity index 51% rename from src/gausskernel/storage/mot/core/storage/sentinel.h rename to src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h index 2d6e8e0b4..bd5234151 100644 --- a/src/gausskernel/storage/mot/core/storage/sentinel.h +++ b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * sentinel.h - * Primary/Secondary index sentinel. + * Base Index Sentinel * * IDENTIFICATION * src/gausskernel/storage/mot/core/storage/sentinel.h @@ -29,51 +29,65 @@ #include #include +#include +#include #include - -#include "key.h" +#include "cycles.h" #include "index_defs.h" #include "utilities.h" #include "mot_atomic_ops.h" #include "debug_utils.h" +#include "mm_def.h" +#include "gc_shared_info.h" +#include "primary_sentinel_node.h" namespace MOT { // forward declarations class Row; class Index; +class PrimarySentinel; + +const char* const enIndexOrder[] = { + stringify(INDEX_ORDER_PRIMARY), stringify(INDEX_ORDER_SECONDARY), stringify(INDEX_ORDER_SECONDARY_UNIQUE)}; + +enum SentinelFlags : uint64_t { + S_DIRTY_BIT = 1UL << 63, // Dirty bit + S_LOCK_BIT = 1UL << 62, // Lock bit + S_WRITE_BIT = 1UL << 61, // Write bit + S_LOCK_WRITE_BITS = (S_LOCK_BIT | S_WRITE_BIT), + S_COUNTER_LOCK_BIT = 1UL << 31, + S_STATUS_BITS = (S_DIRTY_BIT | S_LOCK_BIT), + S_LOCK_OWNER_SIZE = 12, + S_COUNTER_SIZE = 31, + S_OBJ_ADDRESS_SIZE = 48, + S_OBJ_ADDRESS_MASK = (1UL << S_OBJ_ADDRESS_SIZE) - 1, + S_LOCK_OWNER_MASK = ((1UL << S_LOCK_OWNER_SIZE) - 1) << S_OBJ_ADDRESS_SIZE, + S_LOCK_OWNER_RESIZE = (1UL << S_LOCK_OWNER_SIZE) - 1, + S_COUNTER_MASK = ((1UL << S_COUNTER_SIZE) - 1), + S_STATUS_MASK = ~S_STATUS_BITS +}; /** * @class Sentinel - * @brief Primary/Secondary index sentinel. + * @brief Base index sentinel. */ class Sentinel { public: - enum SentinelFlags : uint64_t { - S_DIRTY_BIT = 1UL << 63, // dirty bit - S_LOCK_BIT = 1UL << 62, // lock bit - S_COUNTER_LOCK_BIT = 1UL << 31, - S_PRIMARY_INDEX_BIT = 1UL << 61, - S_STATUS_BITS = (S_DIRTY_BIT | S_LOCK_BIT | S_PRIMARY_INDEX_BIT), - S_LOCK_OWNER_SIZE = 12, - S_COUNTER_SIZE = 31, - S_OBJ_ADDRESS_SIZE = 48, - S_OBJ_ADDRESS_MASK = (1UL << S_OBJ_ADDRESS_SIZE) - 1, - S_LOCK_OWNER_MASK = ((1UL << S_LOCK_OWNER_SIZE) - 1) << S_OBJ_ADDRESS_SIZE, - S_LOCK_OWNER_RESIZE = (1UL << S_LOCK_OWNER_SIZE) - 1, - S_COUNTER_MASK = ((1UL << S_COUNTER_SIZE) - 1), - S_STATUS_MASK = ~S_STATUS_BITS - }; + virtual ~Sentinel() + { + m_index = nullptr; + } enum StableRowFlags : uint64_t { STABLE_BIT = 1UL << 63, PRE_ALLOC_BIT = 1UL << 62 }; inline bool IsCommited() const { - return (m_status & S_DIRTY_BIT) == 0; + return (GetStatus() & S_DIRTY_BIT) == 0; } inline bool IsDirty() const { - return (m_status & S_DIRTY_BIT) == S_DIRTY_BIT; + return (GetStatus() & S_DIRTY_BIT) == S_DIRTY_BIT; } inline void UnSetDirty() @@ -81,14 +95,29 @@ public: m_status &= ~S_DIRTY_BIT; } + inline bool IsPrimarySentinel() const + { + return (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY); + } + inline void SetPrimaryIndex() { - m_status |= S_PRIMARY_INDEX_BIT; + m_indexOrder = IndexOrder::INDEX_ORDER_PRIMARY; + } + + inline void SetIndexOrder(IndexOrder order) + { + m_indexOrder = order; + } + + inline IndexOrder GetIndexOrder() const + { + return m_indexOrder; } inline bool IsPrimaryIndex() const { - return (m_status & S_PRIMARY_INDEX_BIT) == S_PRIMARY_INDEX_BIT; + return (m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY); } inline bool IsCommitedORrPrimaryIndex() const @@ -96,27 +125,32 @@ public: return (IsCommited() or IsPrimaryIndex()); } - inline bool IsCommitedRAndPrimaryIndex() const + inline bool IsCommitedRAndSecondaryIndex() const { - return (IsCommited() and IsPrimaryIndex()); + return (IsCommited() and !IsPrimaryIndex()); } inline uint32_t GetCounter() const { - return (m_refCount & S_COUNTER_MASK); + return (GetRefCount() & S_COUNTER_MASK); } - inline uint64_t GetLockOwner() const + inline uint32_t GetRefCount() const { - return (m_status & S_LOCK_OWNER_SIZE) >> S_OBJ_ADDRESS_SIZE; + return MOT_ATOMIC_LOAD(m_refCount); + } + + inline bool IsCounterReachedSoftLimit() const + { + return GetCounter() > 16; } void SetUpgradeCounter() { LockRefCount(); DecCounter(); - ReleaseRefCount(); - MOT_ASSERT(IsLocked() == true); + UnlockRefCount(); + MOT_ASSERT(IsLocked()); } inline void SetIndex(Index* index) @@ -134,7 +168,6 @@ public: m_status |= S_DIRTY_BIT; } - // Set dirty and init counter to 1 inline void InitStatus() { m_status = S_DIRTY_BIT; @@ -145,18 +178,18 @@ public: m_refCount |= 1; } - inline void Release() + inline void Unlock() { MOT_ASSERT(m_status & S_LOCK_BIT); #ifdef MOT_DEBUG SetLockOwner(-1); #endif #if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) - m_status = m_status & (~S_LOCK_BIT); + m_status = m_status & (~S_LOCK_WRITE_BITS); #else uint64_t v = m_status; - while (!__sync_bool_compare_and_swap(&m_status, v, (v & ~S_LOCK_BIT))) { + while (!__sync_bool_compare_and_swap(&m_status, v, (v & ~S_LOCK_WRITE_BITS))) { PAUSE v = m_status; } @@ -165,24 +198,40 @@ public: inline bool IsLocked() const { - return (m_status & S_LOCK_BIT) == S_LOCK_BIT; + return (GetStatus() & S_LOCK_BIT) == S_LOCK_BIT; + } + + inline bool IsLockedAndDirty() const + { + return (GetStatus() & S_STATUS_BITS) == S_STATUS_BITS; } void Lock(uint64_t tid) { - uint64_t v = m_status; + uint64_t v = GetStatus(); while ((v & S_LOCK_BIT) || !__sync_bool_compare_and_swap(&m_status, v, v | S_LOCK_BIT)) { PAUSE - v = m_status; + v = GetStatus(); } #ifdef MOT_DEBUG SetLockOwner(tid); #endif } + void SetWriteBit() + { + MOT_ASSERT(IsLocked()); + m_status |= S_WRITE_BIT; + } + + inline bool IsWriteBitOn() const + { + return (GetStatus() & S_WRITE_BIT) == S_WRITE_BIT; + } + bool TryLock(uint64_t tid) { - uint64_t v = m_status; + uint64_t v = GetStatus(); if (v & S_LOCK_BIT) { // already locked return false; } @@ -197,14 +246,14 @@ public: void LockRefCount() { - uint32_t v = m_refCount; + uint32_t v = GetRefCount(); while ((v & S_COUNTER_LOCK_BIT) || !__sync_bool_compare_and_swap(&m_refCount, v, v | S_COUNTER_LOCK_BIT)) { PAUSE - v = m_refCount; + v = GetRefCount(); } } - inline void ReleaseRefCount() + inline void UnlockRefCount() { MOT_ASSERT(m_refCount & S_COUNTER_LOCK_BIT); @@ -212,15 +261,22 @@ public: m_refCount = m_refCount & (~S_COUNTER_LOCK_BIT); #else - uint64_t v = m_refCount; + uint64_t v = GetRefCount(); while (!__sync_bool_compare_and_swap(&m_refCount, v, (v & ~S_COUNTER_LOCK_BIT))) { PAUSE - v = m_refCount; + v = GetRefCount(); } #endif } - RC RefCountUpdate(AccessType type, uint64_t tid); + RC RefCountUpdate(AccessType type); + RC RefCountUpdateNoLock(AccessType type); + + /** + * @brief Rollback upgrade insert + * @param RC Indicate if this sentinel can be deleted + */ + RC RollBackUnique(); /** * @brief Sets the next versioned data pointer. @@ -229,97 +285,57 @@ public: void Init(Index* index, Row* row) { m_index = index; + m_startCSN = 0; InitStatus(); InitCounter(); SetNextPtr(row); - m_stable = 0; } + /** * @brief Set the object pointer for the sentinel * PS - points to a row * SS - points to a secondary sentinel + * SSU - points to a secondary + * sentinel unique node * @param ptr - the object pointer */ void SetNextPtr(void* const& ptr) { - m_status = (m_status & ~S_OBJ_ADDRESS_MASK) | ((uint64_t)(ptr)&S_OBJ_ADDRESS_MASK); + m_status = (m_status & ~S_OBJ_ADDRESS_MASK) | ((uint64_t)(ptr) & S_OBJ_ADDRESS_MASK); } /** * @brief Retrieves the data associated with the sentinel index entry. * @return The sentinel data. */ - Row* GetData(void) const + virtual Row* GetData(void) const { - if (IsPrimaryIndex()) { - return reinterpret_cast((uint64_t)(m_status & S_OBJ_ADDRESS_MASK)); - } else { - Sentinel* s = reinterpret_cast((uint64_t)(m_status & S_OBJ_ADDRESS_MASK)); - if (s != nullptr) { - return s->GetData(); - } else { - return nullptr; - } - } + MOT_ASSERT(false); + return nullptr; } /** - * @brief Retrieves the key associated with the sentinel index - * entry. - * @return The primary sentinel address. + * @brief Retrieves the row for the current CSN + * @param csn the current snapshot + * @return The sentinel data. */ - void* GetPrimarySentinel() const - { - if (IsPrimaryIndex()) { - return reinterpret_cast(const_cast(this)); - } else { - return reinterpret_cast((uint64_t)(m_status & S_OBJ_ADDRESS_MASK)); - } - } - - inline bool GetStableStatus() const - { - return (m_stable & STABLE_BIT) == STABLE_BIT; - } - - inline void SetStableStatus(bool val) - { - if (val == true) { - m_stable |= STABLE_BIT; - } else { - m_stable &= ~STABLE_BIT; - } - } + virtual Row* GetVisibleRowVersion(uint64_t csn) = 0; /** - * @brief Sets the current row as stable for CALC + * @brief Retrieves the primarySentinel for the current CSN + * @param csn the current snapshot + * @return The primary sentinel. */ - void SetStable(Row* const& row) - { - m_stable = (m_stable & ~S_OBJ_ADDRESS_MASK) | ((uint64_t)(row)&S_OBJ_ADDRESS_MASK); - } + virtual PrimarySentinel* GetVisiblePrimaryHeader(uint64_t csn) = 0; /** - * @brief Retrieves the stable row - * @return The row object. + * @brief Retrieves the primary sentinel associated with the current sentinel + * @return void pointer to the primary sentinel. */ - Row* GetStable() + virtual void* GetPrimarySentinel() const { - return reinterpret_cast((uint64_t)(m_stable & S_OBJ_ADDRESS_MASK)); - }; - - inline bool GetStablePreAllocStatus() const - { - return (m_stable & PRE_ALLOC_BIT) == PRE_ALLOC_BIT; - } - - inline void SetStablePreAllocStatus(bool val) - { - if (val == true) { - m_stable |= PRE_ALLOC_BIT; - } else { - m_stable &= ~PRE_ALLOC_BIT; - } + MOT_ASSERT(false); + return nullptr; } void SetLockOwner(uint64_t tid) @@ -329,6 +345,59 @@ public: m_status = (m_status & ~S_LOCK_OWNER_MASK) | (owner << S_OBJ_ADDRESS_SIZE); } + virtual bool GetStableStatus() const + { + return false; + } + + virtual void SetStableStatus(bool val) + {} + + /** + * @brief Sets the current row as stable for CALC + */ + virtual void SetStable(Row* const& row) + {} + + /** + * @brief Retrieves the stable row + * @return The row object. + */ + virtual Row* GetStable() const + { + return nullptr; + } + + uint64_t GetStartCSN() const + { + return m_startCSN; + } + + uint64_t GetStatus() const + { + return MOT_ATOMIC_LOAD(m_status); + } + + void SetStartCSN(uint64_t startCSN) + { + m_startCSN = startCSN; + } + + virtual void SetEndCSN(uint64_t endCSN) + { + MOT_ASSERT(false); + } + + virtual void Print() = 0; + + static constexpr uint64_t SENTINEL_INIT_CSN = static_cast(-1); + + static constexpr uint64_t SENTINEL_INVISIBLE_CSN = 0x1FFFFFFFFFFFFFFFUL; + +protected: + /** @var m_startCSN The start TS the sentinel was created */ + uint64_t m_startCSN = SENTINEL_INIT_CSN; + private: /** @var m_status Contains, the pointer and the status bit */ volatile uint64_t m_status = 0; @@ -336,25 +405,28 @@ private: /** @var m_index The index the sentinel belongs to */ Index* m_index = nullptr; - /** @var m_stable A Container for the row and its status bit */ - volatile uint64_t m_stable = 0; - /** @var m_refCount A counter of concurrent inserters of the same key */ - volatile uint32_t m_refCount = 0; + uint32_t m_refCount = 0; + + /** @var The order of the index (primary or secondary). */ + IndexOrder m_indexOrder; inline void DecCounter() { MOT_ASSERT(GetCounter() > 0); uint32_t counter = GetCounter() - 1; - m_refCount = (m_refCount & ~S_COUNTER_MASK) | (counter & S_COUNTER_MASK); + m_refCount = (m_refCount & ~S_COUNTER_MASK) | counter; } inline void IncCounter() { uint32_t counter = GetCounter() + 1; - m_refCount = (m_refCount & ~S_COUNTER_MASK) | (counter & S_COUNTER_MASK); + m_refCount = (m_refCount & ~S_COUNTER_MASK) | counter; } + + DECLARE_CLASS_LOGGER() }; + } // namespace MOT #endif // MOT_SENTINEL_H diff --git a/src/gausskernel/storage/mot/core/storage/table.cpp b/src/gausskernel/storage/mot/core/storage/table.cpp index f5f7c410d..9e055c6a9 100644 --- a/src/gausskernel/storage/mot/core/storage/table.cpp +++ b/src/gausskernel/storage/mot/core/storage/table.cpp @@ -23,16 +23,16 @@ */ #include -#include +#include #include #include "table.h" +#include "txn_table.h" #include "mot_engine.h" #include "utilities.h" #include "txn.h" #include "txn_access.h" #include "txn_insert_action.h" #include "redo_log_writer.h" -#include "recovery_manager.h" namespace MOT { IMPLEMENT_CLASS_LOGGER(Table, Storage); @@ -59,20 +59,33 @@ Table::~Table() } free(m_columns); + m_columns = nullptr; } if (m_indexes != nullptr) { free(m_indexes); + m_indexes = nullptr; } if (m_rowPool) { ObjAllocInterface::FreeObjPool(&m_rowPool); } + if (m_tombStonePool) { + ObjAllocInterface::FreeObjPool(&m_tombStonePool); + } + int destroyRc = pthread_rwlock_destroy(&m_rwLock); if (destroyRc != 0) { MOT_LOG_ERROR("~Table: rwlock destroy failed (%d)", destroyRc); } + + destroyRc = pthread_mutex_destroy(&m_metaLock); + if (destroyRc != 0) { + MOT_LOG_ERROR("~Table: metaLock destroy failed (%d)", destroyRc); + } + + m_primaryIndex = nullptr; } bool Table::Init(const char* tableName, const char* longName, unsigned int fieldCnt, uint64_t tableExId) @@ -83,11 +96,17 @@ bool Table::Init(const char* tableName, const char* longName, unsigned int field return false; } - m_tableName.assign(tableName); - m_longTableName.assign(longName); + initRc = pthread_mutex_init(&m_metaLock, NULL); + if (initRc != 0) { + MOT_LOG_ERROR("failed to initialize Table %s, could not init metalock (%d)", tableName, initRc); + return false; + } + + (void)m_tableName.assign(tableName); + (void)m_longTableName.assign(longName); // allocate columns - MOT_LOG_DEBUG("GC Create table id %d table name %s table addr = %p \n", m_tableId, tableName, this); + MOT_LOG_DEBUG("GC Create table id %d table name %s table addr = %p", m_tableId, tableName, this); this->m_columns = (Column**)memalign(CL_SIZE, fieldCnt * sizeof(Column*)); if (m_columns == nullptr) { return false; @@ -108,7 +127,7 @@ bool Table::Init(const char* tableName, const char* longName, unsigned int field this->m_tupleSize = 0; this->m_maxFields = (unsigned int)fieldCnt; - MOT_LOG_DEBUG("Table::%s %s TableId:%d ExId:%d\n", __func__, this->m_longTableName.c_str(), m_tableId, m_tableExId); + MOT_LOG_DEBUG("Table::%s %s TableId:%d ExId:%d", __func__, this->m_longTableName.c_str(), m_tableId, m_tableExId); return true; } @@ -124,6 +143,18 @@ bool Table::InitRowPool(bool local) return result; } +bool Table::InitTombStonePool(bool local) +{ + bool result = true; + m_tombStonePool = ObjAllocInterface::GetObjPool(sizeof(Row), local); + if (!m_tombStonePool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Initialize Table", "Failed to allocate row pool for table %s", m_longTableName.c_str()); + result = false; + } + return result; +} + void Table::ClearThreadMemoryCache() { for (int i = 0; i < m_numIndexes; i++) { @@ -135,6 +166,10 @@ void Table::ClearThreadMemoryCache() if (m_rowPool != nullptr) { m_rowPool->ClearThreadCache(); } + + if (m_tombStonePool != nullptr) { + m_tombStonePool->ClearThreadCache(); + } } void Table::IncIndexColumnUsage(MOT::Index* index) @@ -192,9 +227,7 @@ bool Table::UpdatePrimaryIndex(MOT::Index* index, TxnManager* txn, uint32_t tid) if (this->m_primaryIndex) { DecIndexColumnUsage(this->m_primaryIndex); if (txn == nullptr) { - if (DeleteIndex(this->m_primaryIndex) != RC_OK) { - return false; - } + DeleteIndex(this->m_primaryIndex); } else { if (txn->DropIndex(this->m_primaryIndex) != RC_OK) { return false; @@ -212,16 +245,17 @@ bool Table::UpdatePrimaryIndex(MOT::Index* index, TxnManager* txn, uint32_t tid) return true; } -RC Table::DeleteIndex(MOT::Index* index) +void Table::DeleteIndex(MOT::Index* index) { GcManager::ClearIndexElements(index->GetIndexId()); delete index; - - return RC::RC_OK; } bool Table::AddSecondaryIndex(const string& indexName, MOT::Index* index, TxnManager* txn, uint32_t tid) { + index->SetTable(this); + IncIndexColumnUsage(index); + // Should we check for duplicate indices with same name? // first create secondary index data bool createdIndexData = @@ -237,9 +271,6 @@ bool Table::AddSecondaryIndex(const string& indexName, MOT::Index* index, TxnMan // add index to table structure after the data is in place // this order prevents index usage before all rows are indexed - index->SetTable(this); - IncIndexColumnUsage(index); - m_secondaryIndexes[indexName] = index; m_indexes[m_numIndexes] = index; ++m_numIndexes; @@ -262,7 +293,7 @@ bool Table::CreateSecondaryIndexDataNonTransactional(MOT::Index* index, uint32_t // iterate over primary index and insert secondary index keys while (it->IsValid()) { Row* row = it->GetRow(); - if (row == nullptr) { + if (row == nullptr or row->IsRowDeleted()) { it->Next(); continue; } @@ -290,7 +321,6 @@ bool Table::CreateSecondaryIndexData(MOT::Index* index, TxnManager* txn) { RC status = RC_OK; bool error = false; - Key* key = nullptr; bool ret = true; IndexIterator* it = m_indexes[0]->Begin(txn->GetThdId()); @@ -300,14 +330,16 @@ bool Table::CreateSecondaryIndexData(MOT::Index* index, TxnManager* txn) return false; } + // increment statement count to avoid not seeing inserted index rows... + // this was found as part of a RC_LOCAL_ROW_NOT_VISIBLE on index creation + // in recovery + txn->IncStmtCount(); + do { // return if empty - if (!it->IsValid()) + if (!it->IsValid()) { break; - - // increment statement count to avoid not seeing inserted index rows... - // this was found as part of a RC_LOCAL_ROW_NOT_VISIBLE on index creation - txn->IncStmtCount(); + } // iterate over primary index and insert secondary index keys while (it->IsValid()) { @@ -321,9 +353,7 @@ bool Table::CreateSecondaryIndexData(MOT::Index* index, TxnManager* txn) case RC::RC_LOCAL_ROW_NOT_FOUND: break; case RC::RC_LOCAL_ROW_FOUND: - if (row == nullptr) { - row = tmpRow; - } + row = tmpRow; break; case RC::RC_MEMORY_ALLOCATION_ERROR: // error handling @@ -333,25 +363,29 @@ bool Table::CreateSecondaryIndexData(MOT::Index* index, TxnManager* txn) break; } - if (row == nullptr) { + if (row == nullptr or row->IsRowDeleted()) { it->Next(); continue; } if (!error) { - key = txn->GetTxnKey(index); - index->BuildKey(this, row, key); - txn->GetNextInsertItem()->SetItem(row, index, key); - status = txn->InsertRow(row); - if (status != RC_OK) { + InsItem* insItem = txn->GetNextInsertItem(); + if (insItem == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Insert Row", "Cannot get insert item"); error = true; - if (MOT_IS_SEVERE()) { // report to error stack only in severe error conditions (we do not want to - // burden "unique violation" scenario) - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Create Secondary Index", - "Failed to insert row into unique secondary index %s in table %s", - index->GetName().c_str(), - m_longTableName.c_str()); + } else { + insItem->SetItem(row, index); + status = txn->InsertRow(row); + if (status != RC_OK) { + error = true; + // report to error stack only in severe error conditions (avoid log flooding) + if (MOT_IS_SEVERE()) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Create Secondary Index", + "Failed to insert row into unique secondary index %s in table %s", + index->GetName().c_str(), + m_longTableName.c_str()); + } } } } @@ -408,22 +442,22 @@ RC Table::InsertRowNonTransactional(Row* row, uint64_t tid, Key* k, bool skipSec pk->InitKey(ix->GetKeyLength()); // set primary key if (ix->IsFakePrimary()) { - surrogateprimaryKey = _surr_gen.GetSurrogateKey(MOT_GET_CURRENT_CONNECTION_ID()); - surrogateprimaryKey = htobe64(surrogateprimaryKey); - row->SetSurrogateKey(surrogateprimaryKey); + surrogateprimaryKey = row->GetSurrogateKey(); + row->SetSurrogateKey(); pk->CpKey((uint8_t*)&surrogateprimaryKey, sizeof(uint64_t)); } else { ix->BuildKey(this, row, pk); } } - Sentinel* res = ix->IndexInsert(pk, row, tid); + Sentinel* res = ix->IndexInsert(pk, row, tid); if (res == nullptr) { if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Insert row", "Failed to insert row to index"); } return MOT_GET_LAST_ERROR_RC(); } else { + res->SetStartCSN(row->GetCommitSequenceNumber()); row->SetPrimarySentinel(res); } @@ -433,7 +467,8 @@ RC Table::InsertRowNonTransactional(Row* row, uint64_t tid, Key* k, bool skipSec ix = GetSecondaryIndex(i); key.InitKey(ix->GetKeyLength()); ix->BuildKey(this, row, &key); - if (ix->IndexInsert(&key, row, tid) == nullptr) { + res = ix->IndexInsert(&key, row, tid); + if (res == nullptr) { if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Insert row", @@ -443,6 +478,10 @@ RC Table::InsertRowNonTransactional(Row* row, uint64_t tid, Key* k, bool skipSec } return MOT_GET_LAST_ERROR_RC(); } + res->SetStartCSN(row->GetCommitSequenceNumber()); + if (ix->GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + static_cast(res)->GetTopNode()->SetStartCSN(row->GetCommitSequenceNumber()); + } } } @@ -451,150 +490,152 @@ RC Table::InsertRowNonTransactional(Row* row, uint64_t tid, Key* k, bool skipSec RC Table::InsertRow(Row* row, TxnManager* txn) { - MOT::Key* key = nullptr; - uint64_t surrogateprimaryKey = 0; MOT::Index* ix = GetPrimaryIndex(); uint32_t numIndexes = GetNumIndexes(); - MOT::Key* cleanupKeys[numIndexes] = {nullptr}; // add row // set primary key row->SetRowId(txn->GetSurrogateKey()); - key = txn->GetTxnKey(ix); - if (key == nullptr) { - DestroyRow(row); - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Insert Row", "Failed to create primary key"); - return RC_MEMORY_ALLOCATION_ERROR; - } - cleanupKeys[0] = key; if (ix->IsFakePrimary()) { - surrogateprimaryKey = htobe64(row->GetRowId()); - row->SetSurrogateKey(surrogateprimaryKey); - key->CpKey((uint8_t*)&surrogateprimaryKey, sizeof(uint64_t)); - } else { - ix->BuildKey(this, row, key); + row->SetSurrogateKey(); } - txn->GetNextInsertItem()->SetItem(row, ix, key); + InsItem* insItem = txn->GetNextInsertItem(); + if (insItem == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Insert Row", "Cannot get insert item for inserting a row"); + return RC_MEMORY_ALLOCATION_ERROR; + } + insItem->SetItem(row, ix); // add secondary indexes for (uint16_t i = 1; i < numIndexes; i++) { ix = GetSecondaryIndex(i); - key = txn->GetTxnKey(ix); - if (key == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Insert Row", "Failed to create key for secondary index %s", ix->GetName().c_str()); - for (uint16_t j = 0; j < numIndexes; j++) { - if (cleanupKeys[j] != nullptr) { - MOTCurrTxn->DestroyTxnKey(cleanupKeys[j]); - } - } - DestroyRow(row); - MOTCurrTxn->Rollback(); + insItem = txn->GetNextInsertItem(); + if (insItem == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Insert Row", "Cannot get insert item for inserting a row"); return RC_MEMORY_ALLOCATION_ERROR; } - cleanupKeys[i] = key; - ix->BuildKey(this, row, key); - txn->GetNextInsertItem()->SetItem(row, ix, key); + insItem->SetItem(row, ix); } return txn->InsertRow(row); } -Row* Table::RemoveRow(Row* row, uint64_t tid, GcManager* gc) +PrimarySentinel* Table::GCRemoveRow(GcQueue::DeleteVector* deletes, Row* tombstone, GC_OPERATION_TYPE gcOper) { MaxKey key; - Row* OutputRow = nullptr; + PrimarySentinel* OutputSentinel = nullptr; + Row* VersionRow = tombstone->GetNextVersion(); + if (VersionRow == nullptr) { + return nullptr; + } + uint64_t version_csn = VersionRow->GetCommitSequenceNumber(); uint32_t numIndexes = GetNumIndexes(); Sentinel* currSentinel = nullptr; // Build keys and mark sentinels for delete for (uint16_t i = 0; i < numIndexes; i++) { MOT::Index* ix = GetIndex(i); + // Check if the scheme is matching the current row + if (version_csn <= ix->GetSnapshot()) { + continue; + } switch (ix->GetIndexOrder()) { case IndexOrder::INDEX_ORDER_PRIMARY: { key.InitKey(ix->GetKeyLength()); - ix->BuildKey(this, row, &key); - currSentinel = ix->IndexReadImpl(&key, tid); + ix->BuildKey(this, VersionRow, &key); + currSentinel = ix->IndexReadImpl(&key, 0); MOT_ASSERT(currSentinel != nullptr); - OutputRow = currSentinel->GetData(); - RC rc = currSentinel->RefCountUpdate(DEC, tid); + MOT_ASSERT(currSentinel->GetData() != nullptr); + // Increase reference count for the reclaim of the primary Sentinel + (void)static_cast(currSentinel)->GetGcInfo().RefCountUpdate(INC); + RC rc = currSentinel->RefCountUpdate(DEC); if (rc == RC::RC_INDEX_DELETE) { - currSentinel = ix->IndexRemove(&key, tid); - if (likely(gc != nullptr)) { - gc->GcRecordObject( - ix->GetIndexId(), currSentinel, nullptr, ix->SentinelDtor, SENTINEL_SIZE(ix)); - gc->GcRecordObject(ix->GetIndexId(), row, nullptr, row->RowDtor, ROW_SIZE_FROM_POOL(this)); - } else { - if (!MOTEngine::GetInstance()->IsRecovering()) { - MOT_LOG_ERROR("RemoveRow called without GC when not recovering"); - return nullptr; - } - DestroyRow(row); - ix->SentinelDtor(currSentinel, nullptr, false); + // At this point the sentinel is detached from the tree! + OutputSentinel = static_cast(currSentinel); + currSentinel = ix->IndexRemove(&key, 0); + MOT_ASSERT(currSentinel->GetCounter() == 0); + MOT_ASSERT(tombstone->GetPrimarySentinel() == currSentinel); + MOT_LOG_DEBUG("Detaching ps %p CSN %lu ", + tombstone->GetPrimarySentinel(), + tombstone->GetCommitSequenceNumber()); + if (gcOper != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + deletes->push_back(currSentinel); } + } else { + (void)static_cast(currSentinel)->GetGcInfo().RefCountUpdate(DEC); + MOT_LOG_DEBUG("Skipping primary cleanup %s %d ", __func__, __LINE__); + continue; } break; } case IndexOrder::INDEX_ORDER_SECONDARY: { key.InitKey(ix->GetKeyLength()); - ix->BuildKey(this, row, &key); - currSentinel = ix->IndexReadImpl(&key, tid); - MOT_ASSERT(currSentinel != nullptr); - RC rc = currSentinel->RefCountUpdate(DEC, tid); - if (rc == RC::RC_INDEX_DELETE) { - currSentinel = ix->IndexRemove(&key, tid); - if (likely(gc != nullptr)) { - gc->GcRecordObject( - ix->GetIndexId(), currSentinel, nullptr, ix->SentinelDtor, SENTINEL_SIZE(ix)); - } else { - if (!MOTEngine::GetInstance()->IsRecovering()) { - MOT_LOG_ERROR("RemoveRow called without GC when not recovering"); - return nullptr; + ix->BuildKey(this, VersionRow, &key); + currSentinel = ix->IndexReadImpl(&key, 0); + if (currSentinel != nullptr) { + RC rc = currSentinel->RefCountUpdate(DEC); + if (rc == RC::RC_INDEX_DELETE) { + MOT_LOG_DEBUG("Releasing SS memory %s %d ", __func__, __LINE__); + currSentinel = ix->IndexRemove(&key, 0); + if (gcOper != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + deletes->push_back(currSentinel); + } else { + ix->ReclaimSentinel(currSentinel); } - ix->SentinelDtor(currSentinel, nullptr, false); } } break; } + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: { + key.InitKey(ix->GetKeyLength()); + ix->BuildKey(this, VersionRow, &key); + currSentinel = ix->IndexReadImpl(&key, 0); + if (currSentinel != nullptr) { + GCRemoveSecondaryUnique(deletes, tombstone, gcOper, ix, currSentinel, &key); + } + break; + } + default: + break; } - MOT_ASSERT(currSentinel != nullptr); - MOT_ASSERT(currSentinel->IsCommited() == true); } - return OutputRow; + return OutputSentinel; } -Row* Table::RemoveKeyFromIndex(Row* row, Sentinel* sentinel, uint64_t tid, GcManager* gc) +void Table::GCRemoveSecondaryUnique(GcQueue::DeleteVector* deletes, Row* tombstone, GC_OPERATION_TYPE gcOper, + MOT::Index* ix, Sentinel* currSentinel, const Key* key) { - MaxKey key; - Row* OutputRow = nullptr; - MOT::Index* ix = sentinel->GetIndex(); - Sentinel* currSentinel = nullptr; - MOT_ASSERT(sentinel != nullptr); - RC rc = sentinel->RefCountUpdate(DEC, tid); + if (currSentinel == nullptr) { + return; + } + + // Multiple threads might try to reclaim concurrently from different positions in the primary + // sentinel node chain. So, we must ensure protection by detaching the unused nodes (cutting the + // chain) within the RefCount lock. + currSentinel->LockRefCount(); + RC rc = currSentinel->RefCountUpdateNoLock(DEC); if (rc == RC::RC_INDEX_DELETE) { - key.InitKey(ix->GetKeyLength()); - ix->BuildKey(this, row, &key); -#ifdef MOT_DEBUG - currSentinel = ix->IndexReadImpl(&key, tid); - MOT_ASSERT(currSentinel == sentinel); - MOT_ASSERT(currSentinel->GetCounter() == 0); -#endif - currSentinel = ix->IndexRemove(&key, tid); - MOT_ASSERT(currSentinel == sentinel); - MOT_ASSERT(currSentinel->GetCounter() == 0); - if (likely(gc != nullptr)) { - if (ix->GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { - OutputRow = currSentinel->GetData(); - MOT_ASSERT(OutputRow != nullptr); - gc->GcRecordObject(ix->GetIndexId(), currSentinel, nullptr, Index::SentinelDtor, SENTINEL_SIZE(ix)); - gc->GcRecordObject(ix->GetIndexId(), OutputRow, nullptr, Row::RowDtor, ROW_SIZE_FROM_POOL(this)); - } else { - gc->GcRecordObject(ix->GetIndexId(), currSentinel, nullptr, Index::SentinelDtor, SENTINEL_SIZE(ix)); - } + currSentinel->UnlockRefCount(); + MOT_LOG_DEBUG("Releasing SS Unique memory %s %d ", __func__, __LINE__); + currSentinel = ix->IndexRemove(key, 0); + if (gcOper != GC_OPERATION_TYPE::GC_OPER_DROP_INDEX) { + deletes->push_back(currSentinel); + } else { + ix->ReclaimSentinel(currSentinel); + } + } else { + // Reclaim deleted nodes + uint64_t snapshot = tombstone->GetCommitSequenceNumber(); + PrimarySentinelNode* detachedChain = nullptr; + static_cast(currSentinel)->DetachUnusedNodes(snapshot, detachedChain); + currSentinel->UnlockRefCount(); + + // Once the detached from the chain, we can safely release the detached nodes (if any). + if (detachedChain != nullptr) { + SecondarySentinelUnique::ReleaseNodeChain(detachedChain, ix); } } - return OutputRow; } Row* Table::CreateNewRow() @@ -606,9 +647,49 @@ Row* Table::CreateNewRow() return row; } +Row* Table::CreateTombStone() +{ + Row* row = m_tombStonePool->Alloc(this); + if (row == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Create Row", "Failed to create new tombstone in table %s", m_longTableName.c_str()); + } + return row; +} + +Row* Table::CreateNewRowCopy(const Row* r, AccessType type) +{ + Row* row = nullptr; + if (type != DEL) { + row = m_rowPool->Alloc(*r, this); + } else { + row = m_tombStonePool->Alloc(this); + } + + if (row == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Create Row", "Failed to copy or create new row in table %s", m_longTableName.c_str()); + return nullptr; + } + if (type == DEL) { + row->CopyHeader(*r); + row->SetDeletedRow(); + } + return row; +} + void Table::DestroyRow(Row* row) { - m_rowPool->Release(row); + if (row->IsRowDeleted() == false) { + // in case of rollback the TxnTable row pool was destroyed + // no need to release the row; + if (IsTxnTable() && m_rowPool == nullptr) { + return; + } + m_rowPool->Release(row); + } else { + m_tombStonePool->Release(row); + } } bool Table::CreateMultipleRows(size_t numRows, Row* rows[]) @@ -657,20 +738,24 @@ RC Table::AddColumn( const char* colName, uint64_t size, MOT_CATALOG_FIELD_TYPES type, bool isNotNull, unsigned int envelopeType) { // validate input parameters - if (!colName || type >= MOT_CATALOG_FIELD_TYPES::MOT_TYPE_UNKNOWN) + if (!colName || type >= MOT_CATALOG_FIELD_TYPES::MOT_TYPE_UNKNOWN) { return RC_UNSUPPORTED_COL_TYPE; + } - if (m_fieldCnt == m_maxFields) + if (m_fieldCnt == m_maxFields) { return RC_TABLE_EXCEEDS_MAX_DECLARED_COLS; + } // column size is uint64_t but tuple size is uint32_t, so we must check for overflow - if (size >= (uint64_t)std::numeric_limitsm_tupleSize)>::max()) + if (size >= (uint64_t)std::numeric_limitsm_tupleSize)>::max()) { return RC_EXCEEDS_MAX_ROW_SIZE; + } decltype(this->m_tupleSize) old_size = m_tupleSize; decltype(this->m_tupleSize) new_size = old_size + size; - if (new_size < old_size) + if (new_size < old_size) { return RC_COL_SIZE_INVALID; + } m_columns[m_fieldCnt] = Column::AllocColumn(type); if (m_columns[m_fieldCnt] == nullptr) { @@ -683,9 +768,12 @@ RC Table::AddColumn( m_longTableName.c_str()); return RC_MEMORY_ALLOCATION_ERROR; } + m_columns[m_fieldCnt]->m_nameLen = strlen(colName); - if (m_columns[m_fieldCnt]->m_nameLen >= Column::MAX_COLUMN_NAME_LEN) + if (m_columns[m_fieldCnt]->m_nameLen >= Column::MAX_COLUMN_NAME_LEN) { return RC_COL_NAME_EXCEEDS_MAX_SIZE; + } + errno_t erc = memcpy_s(&(m_columns[m_fieldCnt]->m_name[0]), Column::MAX_COLUMN_NAME_LEN, colName, @@ -705,6 +793,93 @@ RC Table::AddColumn( return RC_OK; } +RC Table::CreateColumn(Column*& newColumn, const char* colName, uint64_t size, MOT_CATALOG_FIELD_TYPES type, + bool isNotNull, unsigned int envelopeType, bool hasDefault, uintptr_t defValue, size_t defLen) +{ + uint32_t newColCount = m_fieldCnt + 1; + uint64_t newNullBytesSize = BITMAP_GETLEN(newColCount); + bool isNullBytesSizeChanged = false; + // calculate new row size by adding size of new column and difference of NULLBUTES column size + uint32_t newTupleSize = m_tupleSize + size + newNullBytesSize - m_columns[0]->m_size; + + RC rc = RC_OK; + errno_t erc; + + if (!colName || type >= MOT_CATALOG_FIELD_TYPES::MOT_TYPE_UNKNOWN) { + return RC_UNSUPPORTED_COL_TYPE; + } + + uint64_t colId = GetFieldId(colName); + if (colId < m_fieldCnt) { + MOT_LOG_INFO("The column %s already exists in table %s", colName, GetTableName().c_str()); + return rc; + } + + // column size is uint64_t but tuple size is uint32_t, so we must check for overflow + if (size >= (uint64_t)std::numeric_limitsm_tupleSize)>::max()) { + return RC_EXCEEDS_MAX_ROW_SIZE; + } + + if (newTupleSize <= m_tupleSize) { + return RC_COL_SIZE_INVALID; + } + + if (newTupleSize >= MAX_TUPLE_SIZE) { + return RC_EXCEEDS_MAX_ROW_SIZE; + } + + if (newNullBytesSize > m_columns[0]->m_size) { + isNullBytesSizeChanged = true; + } + + // actually allocate new column + do { + newColumn = Column::AllocColumn(type); + if (newColumn == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Add Column", + "Failed to allocate column %s of type %u, having size %" PRIu64 " bytes, for table %s", + colName, + (unsigned)type, + size, + m_longTableName.c_str()); + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + newColumn->m_nameLen = strlen(colName); + if (newColumn->m_nameLen >= Column::MAX_COLUMN_NAME_LEN) { + rc = RC_COL_NAME_EXCEEDS_MAX_SIZE; + break; + } + erc = memcpy_s(&(newColumn->m_name[0]), Column::MAX_COLUMN_NAME_LEN, colName, newColumn->m_nameLen + 1); + securec_check(erc, "\0", "\0"); + + newColumn->m_type = type; + newColumn->m_size = size; + newColumn->m_id = m_fieldCnt; + newColumn->m_offset = (isNullBytesSizeChanged ? m_tupleSize + 1 : m_tupleSize); + newColumn->m_isNotNull = isNotNull; + newColumn->m_envelopeType = envelopeType; + newColumn->m_isCommitted = false; + newColumn->SetKeySize(); + if (hasDefault) { + rc = newColumn->SetDefaultValue(defValue, defLen); + if (rc != RC_OK) { + MOT_LOG_ERROR("Failed to set default value for column %s", colName); + break; + } + } + } while (false); + + if (rc != RC_OK) { + if (newColumn != nullptr) { + delete newColumn; + newColumn = nullptr; + } + } + return rc; +} + bool Table::ModifyColumnSize(const uint32_t& id, const uint64_t& size) { // validate input parameters @@ -718,7 +893,7 @@ bool Table::ModifyColumnSize(const uint32_t& id, const uint64_t& size) } uint64_t oldColSize = m_columns[id]->m_size; - uint64_t newTupleSize = ((uint64_t)m_tupleSize) - oldColSize + size; + uint64_t newTupleSize = (((uint64_t)m_tupleSize) - oldColSize) + size; if (newTupleSize >= (uint64_t)std::numeric_limitsm_tupleSize)>::max()) { return false; } @@ -728,7 +903,7 @@ bool Table::ModifyColumnSize(const uint32_t& id, const uint64_t& size) // now we need to fix the offset of all subsequent fields for (uint32_t i = id + 1; i < m_fieldCnt; ++i) { - m_columns[id]->m_offset = m_columns[id]->m_offset - oldColSize + size; + m_columns[id]->m_offset = (m_columns[id]->m_offset - oldColSize) + size; } return true; @@ -749,51 +924,26 @@ uint64_t Table::GetFieldId(const char* name) const return (i < m_fieldCnt ? i : (uint64_t)-1); } -void Table::PrintSchema() +void Table::PrintSchema() const { - printf("\n[Table] %s\n", m_tableName.c_str()); + (void)printf("\n[Table] %s\n", m_tableName.c_str()); for (uint32_t i = 0; i < m_fieldCnt; i++) { - printf("\t%s\t%s\t%lu\n", GetFieldName(i), GetFieldTypeStr(i), GetFieldSize(i)); + (void)printf("\t%s\t%s\t%lu\n", GetFieldName(i), GetFieldTypeStr(i), GetFieldSize(i)); } } -void Table::Truncate(TxnManager* txn) -{ - uint32_t pid = txn->GetThdId(); - (void)pthread_rwlock_wrlock(&m_rwLock); - - // first destroy secondary index data - for (int i = 1; i < m_numIndexes; i++) { - GcManager::ClearIndexElements(m_indexes[i]->GetIndexId()); - m_indexes[i]->Truncate(false); - } - - // destroy primary index data and row data - GcManager::ClearIndexElements(m_indexes[0]->GetIndexId()); - m_indexes[0]->Truncate(false); - ObjAllocInterface::FreeObjPool(&m_rowPool); - m_rowPool = ObjAllocInterface::GetObjPool(sizeof(Row) + m_tupleSize, false); - if (!m_rowPool) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Truncate Table", - "Failed to allocate row pool after truncate in table %s", - m_longTableName.c_str()); - } - - (void)pthread_rwlock_unlock(&m_rwLock); -} - void Table::Compact(TxnManager* txn) { uint32_t pid = txn->GetThdId(); // first destroy secondary index data for (int i = 0; i < m_numIndexes; i++) { - GcManager::ClearIndexElements(m_indexes[i]->GetIndexId(), false); + GcManager::ClearIndexElements(m_indexes[i]->GetIndexId(), GC_OPERATION_TYPE::GC_OPER_TRUNCATE); m_indexes[i]->Compact(this, pid); } + ClearThreadMemoryCache(); } -uint64_t Table::GetTableSize() +uint64_t Table::GetTableSize(uint64_t& netTotal) { PoolStatsSt stats; errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); @@ -801,31 +951,53 @@ uint64_t Table::GetTableSize() stats.m_type = PoolStatsT::POOL_STATS_ALL; m_rowPool->GetStats(stats); - if (MOT_CHECK_LOG_LEVEL(LogLevel::LL_TRACE)) { - m_rowPool->PrintStats(stats, "GC_TEST", LogLevel::LL_TRACE); - } + m_rowPool->PrintStats(stats, "Row Pool", LogLevel::LL_INFO); + uint64_t res = stats.m_poolCount * stats.m_poolGrossSize; - uint64_t netto = (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + netTotal = (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); securec_check(erc, "\0", "\0"); stats.m_type = PoolStatsT::POOL_STATS_ALL; - MOT_LOG_INFO("Table %s memory size: gross: %lu", m_tableName.c_str(), res); + m_tombStonePool->GetStats(stats); + m_tombStonePool->PrintStats(stats, "TombStone Pool", LogLevel::LL_INFO); + + res += stats.m_poolCount * stats.m_poolGrossSize; + netTotal += (stats.m_totalObjCount - stats.m_freeObjCount) * stats.m_objSize; + + MOT_LOG_INFO("Table %s memory size - Gross: %lu, NetTotal: %lu", m_tableName.c_str(), res, netTotal); return res; } -size_t Table::SerializeItemSize(Column* column) +size_t Table::SerializeItemSize(Column* column) const { size_t ret = SerializableARR::SerializeSize(column->m_name) + SerializablePOD::SerializeSize(column->m_size) + SerializablePOD::SerializeSize(column->m_type) + SerializablePOD::SerializeSize(column->m_isNotNull) + - SerializablePOD::SerializeSize(column->m_envelopeType); // required for MOT JIT + SerializablePOD::SerializeSize(column->m_envelopeType) + // required for MOT JIT + SerializablePOD::SerializeSize(column->m_isDropped) + + SerializablePOD::SerializeSize(column->m_hasDefault); + if (column->m_hasDefault) { + switch (column->m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: + ret += SerializableCharBuf::SerializeSize(column->m_defSize); + break; + default: + ret += SerializablePOD::SerializeSize(column->m_defValue); + break; + } + } return ret; } -char* Table::SerializeItem(char* dataOut, Column* column) +char* Table::SerializeItem(char* dataOut, Column* column) const { if (!column || !dataOut) { return nullptr; @@ -835,20 +1007,49 @@ char* Table::SerializeItem(char* dataOut, Column* column) dataOut = SerializablePOD::Serialize(dataOut, column->m_type); dataOut = SerializablePOD::Serialize(dataOut, column->m_isNotNull); dataOut = SerializablePOD::Serialize(dataOut, column->m_envelopeType); // required for MOT JIT + dataOut = SerializablePOD::Serialize(dataOut, column->m_isDropped); + dataOut = SerializablePOD::Serialize(dataOut, column->m_hasDefault); + if (column->m_hasDefault) { + switch (column->m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: + if (column->m_defSize > 0) { + dataOut = + SerializableCharBuf::Serialize(dataOut, (char*)column->m_defValue, (size_t)column->m_defSize); + } else { + dataOut = SerializablePOD::Serialize(dataOut, column->m_defSize); + } + break; + default: + dataOut = SerializablePOD::Serialize(dataOut, column->m_defValue); + break; + } + } return dataOut; } -char* Table::DeserializeMeta(char* dataIn, CommonColumnMeta& meta, uint32_t metaVersion) +char* Table::DeserializeMeta(char* dataIn, CommonColumnMeta& meta, uint32_t metaVersion) const { dataIn = SerializableARR::Deserialize(dataIn, meta.m_name); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_size); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_type); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_isNotNull); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_envelopeType); // required for MOT JIT + if (metaVersion >= MetadataProtoVersion::METADATA_VER_ALTER_COLUMN) { + dataIn = SerializablePOD::Deserialize(dataIn, meta.m_isDropped); + dataIn = SerializablePOD::Deserialize(dataIn, meta.m_hasDefault); + } else { + meta.m_isDropped = false; + meta.m_hasDefault = false; + } return dataIn; } -size_t Table::SerializeItemSize(MOT::Index* index) +size_t Table::SerializeItemSize(MOT::Index* index) const { size_t ret = SerializableSTR::SerializeSize(index->m_name) + SerializablePOD::SerializeSize(index->m_keyLength) + @@ -864,7 +1065,7 @@ size_t Table::SerializeItemSize(MOT::Index* index) return ret; } -char* Table::SerializeItem(char* dataOut, MOT::Index* index) +char* Table::SerializeItem(char* dataOut, MOT::Index* index) const { if (!index || !dataOut) { return nullptr; @@ -894,11 +1095,11 @@ RC Table::RemoveSecondaryIndex(MOT::Index* index, TxnManager* txn) do { SecondaryIndexMap::iterator itr = m_secondaryIndexes.find(index->GetName()); if (MOT_EXPECT_TRUE(itr != m_secondaryIndexes.end())) { - MOT_LOG_DEBUG("logging drop index operation (tableId %u), index name: %s index id = %d \n", + MOT_LOG_DEBUG("Drop index operation (tableId %u), index name: %s index id = %d", GetTableId(), index->GetName().c_str(), index->GetIndexId()); - m_secondaryIndexes.erase(itr); + (void)m_secondaryIndexes.erase(itr); } else { if (m_numIndexes > 0 && (strcmp(m_indexes[0]->GetName().c_str(), index->GetName().c_str()) == 0)) { MOT_LOG_INFO("Trying to remove primary index %s, not supported", index->GetName().c_str()); @@ -926,7 +1127,7 @@ RC Table::RemoveSecondaryIndex(MOT::Index* index, TxnManager* txn) m_indexes[m_numIndexes] = nullptr; } GcManager::ClearIndexElements(index->GetIndexId()); - index->Truncate(true); + (void)index->Truncate(true); DecIndexColumnUsage(index); delete index; @@ -934,9 +1135,8 @@ RC Table::RemoveSecondaryIndex(MOT::Index* index, TxnManager* txn) return res; } -char* Table::DeserializeMeta(char* dataIn, CommonIndexMeta& meta, uint32_t metaVersion) +char* Table::DeserializeMeta(char* dataIn, CommonIndexMeta& meta, uint32_t metaVersion) const { - uint32_t order, method, constr; dataIn = SerializableSTR::Deserialize(dataIn, meta.m_name); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_keyLength); dataIn = SerializablePOD::Deserialize(dataIn, meta.m_indexOrder); @@ -952,8 +1152,8 @@ char* Table::DeserializeMeta(char* dataIn, CommonIndexMeta& meta, uint32_t metaV return dataIn; } -RC Table::CreateIndexFromMeta(CommonIndexMeta& meta, bool primary, uint32_t tid, bool addToTable /* = true */, - MOT::Index** outIndex /* = nullptr */) +RC Table::CreateIndexFromMeta(CommonIndexMeta& meta, bool primary, uint32_t tid, uint32_t metaVersion, + bool addToTable /* = true */, MOT::Index** outIndex /* = nullptr */) { IndexTreeFlavor flavor = DEFAULT_TREE_FLAVOR; MOT::Index* ix = nullptr; @@ -983,6 +1183,16 @@ RC Table::CreateIndexFromMeta(CommonIndexMeta& meta, bool primary, uint32_t tid, return RC_ERROR; } + // re-calculate key length if needed + if (!meta.m_fake && metaVersion < METADATA_VER_IDX_KEY_LEN) { + meta.m_keyLength = 0; + for (int i = 0; i < meta.m_numKeyFields; i++) { + Column* col = GetField(meta.m_columnKeyFields[i]); + meta.m_lengthKeyFields[i] = col->m_keySize; + meta.m_keyLength += col->m_keySize; + } + } + for (int i = 0; i < meta.m_numKeyFields; i++) { ix->SetLenghtKeyFields(i, meta.m_columnKeyFields[i], meta.m_lengthKeyFields[i]); } @@ -1050,7 +1260,6 @@ size_t Table::SerializeSize() void Table::Serialize(char* dataOut) { uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; - char* savedDO = dataOut; dataOut = SerializablePOD::Serialize(dataOut, metaVersion); dataOut = SerializableSTR::Serialize(dataOut, m_tableName); dataOut = SerializableSTR::Serialize(dataOut, m_longTableName); @@ -1125,6 +1334,33 @@ void Table::Deserialize(const char* in) MOT_LOG_ERROR("Table::deserialize - failed to add column %u", i); return; } + m_columns[i]->m_isDropped = col.m_isDropped; + if (col.m_hasDefault) { + switch (col.m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: + SerializableCharBuf::DeserializeSize(dataIn, m_columns[i]->m_defSize); + if (m_columns[i]->m_defSize > 0) { + m_columns[i]->m_defValue = (uintptr_t)malloc(m_columns[i]->m_defSize); + if (m_columns[i]->m_defValue == 0) { + MOT_LOG_ERROR("Table::deserialize - failed to add default for column %u", i); + return; + } + dataIn = SerializableCharBuf::Deserialize( + dataIn, (char*)m_columns[i]->m_defValue, m_columns[i]->m_defSize); + } + break; + default: + dataIn = SerializablePOD::Deserialize(dataIn, m_columns[i]->m_defValue); + m_columns[i]->m_defSize = col.m_size; + break; + } + m_columns[i]->m_hasDefault = true; + } } if (!InitRowPool()) { @@ -1132,11 +1368,16 @@ void Table::Deserialize(const char* in) return; } + if (!InitTombStonePool()) { + MOT_LOG_ERROR("Table::deserialize - failed to create tombstone pool"); + return; + } + if (savedNumIndexes > 0) { CommonIndexMeta idx; /* primary key */ dataIn = DeserializeMeta(dataIn, idx, metaVersion); - if (CreateIndexFromMeta(idx, true, MOTCurrThreadId) != RC_OK) { + if (CreateIndexFromMeta(idx, true, MOTCurrThreadId, metaVersion) != RC_OK) { MOT_LOG_ERROR("Table::deserialize - failed to create primary index"); return; } @@ -1144,7 +1385,7 @@ void Table::Deserialize(const char* in) /* secondaries */ for (uint16_t i = 2; i <= savedNumIndexes; i++) { dataIn = DeserializeMeta(dataIn, idx, metaVersion); - if (CreateIndexFromMeta(idx, false, MOTCurrThreadId) != RC_OK) { + if (CreateIndexFromMeta(idx, false, MOTCurrThreadId, metaVersion) != RC_OK) { MOT_LOG_ERROR("Table::deserialize - failed to create secondary index [%u]", (i - 2)); return; } @@ -1154,24 +1395,27 @@ void Table::Deserialize(const char* in) SetDeserialized(true); } -RC Table::DropImpl() +void Table::DropImpl() { - RC res = RC_OK; - - if (m_numIndexes == 0) - return res; + if (m_numIndexes == 0) { + return; + } (void)pthread_rwlock_wrlock(&m_rwLock); do { + for (int i = 0; i < m_numIndexes; i++) { + if (m_indexes[i] != nullptr) { + GcManager::ClearIndexElements(m_indexes[i]->GetIndexId()); + } + } m_secondaryIndexes.clear(); - MOT_LOG_DEBUG("DropImpl numIndexes = %d \n", m_numIndexes); + MOT_LOG_DEBUG("DropImpl numIndexes = %d", m_numIndexes); for (int i = m_numIndexes - 1; i >= 0; i--) { if (m_indexes[i] != nullptr) { MOT::Index* index = m_indexes[i]; // first remove index from table metadata to prevent it's usage m_indexes[i] = nullptr; - GcManager::ClearIndexElements(index->GetIndexId()); - index->Truncate(true); + (void)index->Truncate(true); DecIndexColumnUsage(index); delete index; } @@ -1179,7 +1423,6 @@ RC Table::DropImpl() m_numIndexes = 0; } while (0); (void)pthread_rwlock_unlock(&m_rwLock); - return res; } Table* Table::CreateDummyTable() @@ -1198,7 +1441,7 @@ Table* Table::CreateDummyTable() MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "Dummy Table Creation", "Failed to add column 'dummy' to dummy table"); } else { - if (!tab->InitRowPool(true)) { + if (!tab->InitRowPool(true) and !tab->InitTombStonePool(true)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Dummy Table Creation", "Failed to initialize row pool, while creating dummy table"); @@ -1263,7 +1506,6 @@ void Table::SerializeRedo(char* dataOut) */ uint16_t numIndexes = 0; uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; - char* savedDO = dataOut; dataOut = SerializablePOD::Serialize(dataOut, metaVersion); dataOut = SerializableSTR::Serialize(dataOut, m_tableName); dataOut = SerializableSTR::Serialize(dataOut, m_longTableName); @@ -1280,4 +1522,21 @@ void Table::SerializeRedo(char* dataOut) dataOut = SerializeItem(dataOut, GetField(i)); } } + +bool Table::IsTableSingleVersion() +{ + MOT_LOG_INFO("Testing table %s", GetTableName().c_str()); + Index* index = GetPrimaryIndex(); + IndexIterator* it = index->Begin(0); + while (it->IsValid()) { + PrimarySentinel* ps = static_cast(it->GetPrimarySentinel()); + Row* r = ps->GetData(); + if (r and r->GetNextVersion()) { + r->Print(); + return false; + } + it->Next(); + } + return true; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/table.h b/src/gausskernel/storage/mot/core/storage/table.h index 9f1e5e1d7..fc14b7bf5 100644 --- a/src/gausskernel/storage/mot/core/storage/table.h +++ b/src/gausskernel/storage/mot/core/storage/table.h @@ -31,8 +31,8 @@ #include #include #include +#include #include "global.h" -#include "sentinel.h" #include "surrogate_key_generator.h" #include "utilities.h" #include "index.h" @@ -41,15 +41,42 @@ #include "serializable.h" #include "object_pool.h" #include "mm_gc_manager.h" +#include "txn_ddl_access.h" namespace MOT { class Row; class TxnManager; class TxnInsertAction; class RecoveryManager; -class TxnDDLAccess; +class Access; +class TxnTable; -enum MetadataProtoVersion : uint32_t { METADATA_VER_INITIAL = 1, METADATA_VER_CURR = METADATA_VER_INITIAL }; +struct rowhashing_func { + uint64_t operator()(const Row* key) const + { + return std::hash()((uint64_t)key); + } +}; + +struct rowkey_equal_fn { + bool operator()(const Row* t1, const Row* t2) const + { + return ((uint64_t)t1 == (uint64_t)t2); + } +}; + +using rowAddrMap_t = std::unordered_map; +using acRowAddrMap_t = std::unordered_multimap; + +enum MetadataProtoVersion : uint32_t { + METADATA_VER_INITIAL = 1, + METADATA_VER_ALTER_COLUMN = 2, // Add/Drop column changes + METADATA_VER_LOW_RTO = 3, + METADATA_VER_IDX_KEY_LEN = 4, + METADATA_VER_IDX_COL_UPD = 5, + METADATA_VER_MVCC = 6, + METADATA_VER_CURR = METADATA_VER_MVCC +}; /** * @class Table @@ -66,9 +93,10 @@ class alignas(CL_SIZE) Table : public Serializable { friend TxnManager; friend TxnInsertAction; friend MOT::Index; - friend MOT::MOTIndexArr; friend RecoveryManager; friend TxnDDLAccess; + friend TxnTable; + friend Row; public: static void deleteTablePtr(Table* t) @@ -79,16 +107,21 @@ public: /** @brief Default constructor. */ Table() : m_rowPool(nullptr), + m_tombStonePool(nullptr), m_primaryIndex(nullptr), m_fieldCnt(0), m_tupleSize(0), m_maxFields(0), + m_filler1(0), + m_filler2(0), + m_tableExId(0), m_deserialized(false), + m_filler4(0), m_rowCount(0) {} /** @brief Destructor. */ - ~Table(); + ~Table() override; // class non-copy-able, non-assignable, non-movable /** @cond EXCLUDE_DOC */ @@ -121,13 +154,19 @@ public: * @brief Initializes row_pool object pool * @return True if initialization succeeded, otherwise false. */ - bool InitRowPool(bool local = false); + virtual bool InitRowPool(bool local = false); + + virtual bool InitTombStonePool(bool local = false); inline uint32_t GetRowSizeFromPool() const { return m_rowPool->m_size; } + inline uint32_t GetTombStoneSizeFromPool() const + { + return m_tombStonePool->m_size; + } /** * @brief Clears object pool thread level cache */ @@ -151,7 +190,7 @@ public: * @brief Retrieves the primary index of the table. * @return The index object. */ - inline MOT::Index* GetPrimaryIndex() const + inline MOT::Index* GetPrimaryIndex() { return m_primaryIndex; } @@ -171,12 +210,12 @@ public: return result; } - inline MOT::Index* GetSecondaryIndex(uint16_t ix) const + inline MOT::Index* GetSecondaryIndex(uint16_t ix) { return (MOT::Index*)m_indexes[ix]; } - inline MOT::Index* GetIndex(uint16_t ix) const + inline MOT::Index* GetIndex(uint16_t ix) { return m_indexes[ix]; } @@ -221,36 +260,6 @@ public: */ RC RemoveSecondaryIndex(MOT::Index* index, TxnManager* txn); - /** - * @brief Remove Index from table meta data. - * @param index The index to use. - * @return void. - */ - void RemoveSecondaryIndexFromMetaData(MOT::Index* index) - { - if (!index->IsPrimaryKey()) { - uint16_t rmIx = 0; - for (uint16_t i = 1; i < m_numIndexes; i++) { - if (m_indexes[i] == index) { - rmIx = i; - break; - } - } - - // prevent removing primary by mistake - if (rmIx > 0) { - DecIndexColumnUsage(index); - m_numIndexes--; - for (uint16_t i = rmIx; i < m_numIndexes; i++) { - m_indexes[i] = m_indexes[i + 1]; - } - - m_secondaryIndexes[index->GetName()] = nullptr; - m_indexes[m_numIndexes] = nullptr; - } - } - } - /** * @brief Add Index to table meta data. * @param index The index to use. @@ -269,9 +278,9 @@ public: /** * @brief Deletes an index. * @param the index to remove. - * @return RC value denoting the operation's completion status. + * @return void. */ - RC DeleteIndex(MOT::Index* index); + void DeleteIndex(MOT::Index* index); /** * @brief Checks if table contains data. @@ -300,7 +309,7 @@ public: void CountAbsents() { uint64_t absentCounter = 0; - MOT_LOG_INFO("Testing table %s \n", GetTableName().c_str()); + MOT_LOG_INFO("Testing table %s", GetTableName().c_str()); for (uint16_t i = 0; i < m_numIndexes; i++) { Index* index = GetIndex(i); IndexIterator* it = index->Begin(0); @@ -311,15 +320,19 @@ public: } it->Next(); } - MOT_LOG_INFO("Found %lu Absents in index %s\n", absentCounter, index->GetName().c_str()); + MOT_LOG_INFO("Found %lu Absents in index %s", absentCounter, index->GetName().c_str()); absentCounter = 0; } } + #endif + bool IsTableSingleVersion(); + void ClearRowCache() { m_rowPool->ClearFreeCache(); + m_tombStonePool->ClearFreeCache(); for (int i = 0; i < m_numIndexes; i++) { if (m_indexes[i] != nullptr) { m_indexes[i]->ClearFreeCache(); @@ -329,9 +342,9 @@ public: /** * @brief table drop hanldler. - * @return RC value denoting the operation's completion status. + * @return void. */ - RC DropImpl(); + void DropImpl(); /** * @brief Increases the column usage on an index. @@ -340,7 +353,7 @@ public: void IncIndexColumnUsage(MOT::Index* index); /** - * @brief Deccreases the column usage on an index. + * @brief Decreases the column usage on an index. * @param index The index to perform on. */ void DecIndexColumnUsage(MOT::Index* index); @@ -356,74 +369,6 @@ public: return m_primaryIndex->Begin(pid, passive); } - /** - * @brief Searches for a row by a secondary index. - * @param indexPtr The secondary index (with unique key indication). - * @param key The key by which to search the row. - * @param result The resulting row (indirected through sItem object). - * @return Result code denoting success or failure reason. - */ - inline RC QuerySecondaryIndex(const MOT::Index* index, Key const* const& key, void*& result) - { - RC rc = RC_OK; - - // read from secondary index - result = index->IndexRead(key, 0); - if (!result) { - rc = RC_ERROR; - } - - MOT_LOG_DIAG2("RC status %u", rc); - return rc; - } - - /** - * @brief Searches for a row by a secondary index. - * @param index The secondary index (with unique key indication). - * @param key The key by which to search the row. - * @param matchKey Specifies whether exact match is required. - * @param result The resulting iterator. - * @param forwardDirection Specifies whether a forward iterator is requested. - * @param pid The identifier of the requesting process/thread. - * @return Result code denoting success or failure reason. - */ - inline RC QuerySecondaryIndex(const MOT::Index* index, Key const* const& key, bool matchKey, IndexIterator*& result, - bool forwardDirection, uint32_t pid) - { - RC rc = RC_OK; - - bool found = false; - result = index->Search(key, matchKey, forwardDirection, pid, found); - if (!found) { - rc = RC_ERROR; - } - - MOT_LOG_DEBUG("RC status %u", rc); - return rc; - } - - /** - * @brief Finds a row by a key using the primary index. - * @param key The key by which to search the row. - * @param result The resulting row. - * @return Return code denoting success or failure reason. - */ - inline RC FindRow(Key const* const& key, Row*& result, const uint32_t& pid) - { - RC rc = RC_ERROR; - - // read from index - Row* row = m_primaryIndex->IndexRead(key, pid); - - // We report a row iff is non-absent - if (row) { - result = row; - rc = RC_OK; - } - - return rc; - } - /** * @brief Finds a row by a key using the primary index. * @param key The key by which to search the row. @@ -434,7 +379,7 @@ public: inline RC FindRowByIndexId(MOT::Index* index, Key const* const& key, Sentinel*& result, const uint32_t& pid) { RC rc = RC_ERROR; - + result = nullptr; // read from index Sentinel* sent = index->IndexReadImpl(key, pid); @@ -457,7 +402,7 @@ public: inline RC FindRow(Key const* const& key, Sentinel*& result, const uint32_t& pid) { RC rc = RC_ERROR; - + result = nullptr; // read from index Sentinel* sent = m_primaryIndex->IndexReadImpl(key, pid); @@ -471,37 +416,6 @@ public: return rc; } - /** - * @brief Finds a row by a key using the primary index. - * @param key The key by which to search the row. - * @param matchKey Specifies whether exact match is required. - * @param result The resulting iterator. - * @param forward_direction Specifies whether a forward iterator is requested. - * @param pid The identifier of the requesting process/thread. - * @return Result code denoting success or failure reason. - */ - inline RC FindRow( - Key const* const& key, bool matchKey, IndexIterator*& result, bool forwardDirection, const uint32_t& pid) - { - RC rc = RC_OK; - - if (matchKey && forwardDirection) { - result = m_primaryIndex->Find(key, pid); - } else if (matchKey && !forwardDirection) { - result = m_primaryIndex->ReverseFind(key, pid); - } else if (!matchKey && forwardDirection) { - result = m_primaryIndex->LowerBound(key, pid); - } else { - result = m_primaryIndex->ReverseLowerBound(key, pid); - } - - if (!result) { - rc = RC_ERROR; - } - - return rc; - } - /** * @brief Queries whether the rows of the table have a fixed length. * @return Boolean value denoting fixed length rows or not. @@ -610,6 +524,10 @@ public: */ Row* CreateNewRow(); + Row* CreateTombStone(); + + Row* CreateNewRowCopy(const Row* r, AccessType type); + /** * @brief Releases a row's memory. * @param row. row to be deleted @@ -647,6 +565,20 @@ public: */ bool ModifyColumnSize(const uint32_t& id, const uint64_t& size); + /** + * @brief Adds a column to the Table during alter table. + * + * The maximum number of columns that can be added to the + * Table is restricted by the field count specified during Table initialization. @see + * Table::init. + * + * @param col_name The name of the column to add. + * @param size The size of the column in bytes. + * @param type The type name of the column. + * @return RC error code. + */ + RC CreateColumn(Column*& newColumn, const char* col_name, uint64_t size, MOT_CATALOG_FIELD_TYPES type, + bool isNotNull, unsigned int envelopeType, bool hasDefault, uintptr_t defValue, size_t defLen); /** * @brief Retrieves the size of a row in the table in bytes. * @return Row size in bytes. @@ -757,7 +689,7 @@ public: /** * @brief Prints the Table */ - void PrintSchema(); + void PrintSchema() const; /** * @brief Updates table row count @@ -780,7 +712,7 @@ public: /** * @brief Returns table size in memory */ - uint64_t GetTableSize(); + uint64_t GetTableSize(uint64_t& netTotal); /** * @brief Returns index size in memory @@ -796,6 +728,19 @@ public: return NULL; } + inline Index* GetIndexByExtIdWithPos(uint64_t extId, int& pos) + { + pos = -1; + for (int i = 0; i < m_numIndexes; i++) { + if (m_indexes[i]->GetExtId() == extId) { + pos = i; + return m_indexes[i]; + } + } + + return NULL; + } + /** * @brief takes a read lock on the table. */ @@ -842,101 +787,8 @@ public: m_deserialized = val; } - /** - * @brief Removes a row from the primary index. - * @param row The row to be removed. - * @param tid The logical identifier of the requesting process/thread. - * @param gc a pointer to the gc object. - * @return The removed row or null pointer if none was found. - */ - Row* RemoveRow(Row* row, uint64_t tid, GcManager* gc = nullptr); + PrimarySentinel* GCRemoveRow(GcQueue::DeleteVector* deletes, Row* tombstone, GC_OPERATION_TYPE gcOper); - Row* RemoveKeyFromIndex(Row* row, Sentinel* sentinel, uint64_t tid, GcManager* gc); - -private: - inline MOT::ObjAllocInterface* GetRowPool() - { - return m_rowPool; - } - - inline void ReplaceRowPool(MOT::ObjAllocInterface* rowPool) - { - ObjAllocInterface::FreeObjPool(&m_rowPool); - m_rowPool = rowPool; - } - - inline void FreeObjectPool(MOT::ObjAllocInterface* rowPool) - { - ObjAllocInterface::FreeObjPool(&rowPool); - } - - /** @var Global atomic table identifier. */ - static std::atomic tableCounter; - - /** @var row_pool personal row allocator object pool */ - ObjAllocInterface* m_rowPool; - - // we have only index-organized-tables (IOT) so this is the pointer to the index - // representing the table - /** @var The primary index holding all rows. */ - MOT::Index* m_primaryIndex; - - /** @var Number of fields in the table schema. */ - uint32_t m_fieldCnt; - - /** @var Size of raw tuple in bytes. */ - uint32_t m_tupleSize; - - /** @var Maximum number of fields in tuple. */ - uint32_t m_maxFields; - - /** @var The number of secondary indices in use. */ - uint16_t m_numIndexes = 0; - - uint16_t _f1; - - /** @var All columns. */ - Column** m_columns = NULL; - - /** @var Secondary index array. */ - MOT::Index** m_indexes = NULL; - - /** @var Current table unique identifier. */ - uint32_t m_tableId = tableCounter++; - - uint32_t _f2; - - uint64_t m_tableExId; - - /** @typedef Secondary index map (indexed by index name). */ - typedef std::map SecondaryIndexMap; - - /** @var Secondary index map accessed by name. */ - SecondaryIndexMap m_secondaryIndexes; - - /** @var RW Lock that guards against deletion during checkpoint/vacuum. */ - pthread_rwlock_t m_rwLock; - - string m_tableName; - - string m_longTableName; - - /** @var Specifies whether rows have fixed length. */ - bool m_fixedLengthRows = true; - - bool m_deserialized; - - /** @var Holds number of rows in the table. The information may not be accurate. - * Used for execution planning. */ - uint8_t _f3[6]; - - uint64_t _f4; - - uint32_t m_rowCount = 0; - - DECLARE_CLASS_LOGGER(); - -public: /** * @brief Creates dummy table with MAX_ROW_SIZE. * @return The new dummy table. @@ -957,6 +809,8 @@ public: bool m_isNotNull; unsigned int m_envelopeType; // required for MOT JIT + bool m_isDropped; + bool m_hasDefault; }; /** @@ -992,7 +846,7 @@ public: * @param column the column to work on * @return Size_t the size */ - size_t SerializeItemSize(Column* column); + size_t SerializeItemSize(Column* column) const; /** * @brief serializes a column into a buffer @@ -1000,7 +854,7 @@ public: * @param column the column to work on * @return Char* the buffer pointer. */ - char* SerializeItem(char* dataOut, Column* column); + char* SerializeItem(char* dataOut, Column* column) const; /** * @brief deserializes a column from a buffer @@ -1008,14 +862,14 @@ public: * @param meta the metadata struct to fill. * @return Char* the buffer pointer. */ - char* DeserializeMeta(char* dataIn, CommonColumnMeta& meta, uint32_t metaVersion); + char* DeserializeMeta(char* dataIn, CommonColumnMeta& meta, uint32_t metaVersion) const; /** * @brief returns the serialized size of an index * @param index the index to work on * @return Size_t the size */ - size_t SerializeItemSize(Index* index); + size_t SerializeItemSize(Index* index) const; /** * @brief serializes an index into a buffer @@ -1023,7 +877,7 @@ public: * @param index the index to work on * @return Char* the buffer pointer. */ - char* SerializeItem(char* dataOut, Index* index); + char* SerializeItem(char* dataOut, Index* index) const; /** * @brief deserializes an index from a buffer @@ -1031,7 +885,7 @@ public: * @param meta the metadata struct to fill. * @return Char* the buffer pointer. */ - char* DeserializeMeta(char* dataIn, CommonIndexMeta& meta, uint32_t metaVersion); + char* DeserializeMeta(char* dataIn, CommonIndexMeta& meta, uint32_t metaVersion) const; /** * @brief creates and index from a metadata struct @@ -1040,8 +894,8 @@ public: * @param tid the thread identifier * @return RC error code. */ - RC CreateIndexFromMeta( - CommonIndexMeta& meta, bool primary, uint32_t tid, bool addToTable = true, Index** outIndex = nullptr); + RC CreateIndexFromMeta(CommonIndexMeta& meta, bool primary, uint32_t tid, uint32_t metaVersion, + bool addToTable = true, Index** outIndex = nullptr); /** * @brief returns the serialized size of a table @@ -1084,6 +938,114 @@ public: * @param dataOut The output buffer */ void SerializeRedo(char* dataOut); + + virtual bool IsTxnTable() + { + return false; + } + virtual Table* GetOrigTable() + { + return this; + } + virtual bool GetHasColumnChanges() const + { + return false; + } + +private: + inline MOT::ObjAllocInterface* GetRowPool() + { + return m_rowPool; + } + + inline void ReplaceRowPool(MOT::ObjAllocInterface* rowPool) + { + ObjAllocInterface::FreeObjPool(&m_rowPool); + m_rowPool = rowPool; + } + + inline void FreeObjectPool(MOT::ObjAllocInterface* rowPool) + { + ObjAllocInterface::FreeObjPool(&rowPool); + } + + void GCRemoveSecondaryUnique(GcQueue::DeleteVector* deletes, Row* tombstone, GC_OPERATION_TYPE gcOper, + MOT::Index* ix, Sentinel* currSentinel, const Key* key); + + /** @var Global atomic table identifier. */ + static std::atomic tableCounter; + + /** @var row_pool personal row allocator object pool */ + ObjAllocInterface* m_rowPool; + + /** @var row_pool personal row allocator object pool */ + ObjAllocInterface* m_tombStonePool; + + // we have only index-organized-tables (IOT) so this is the pointer to the index + // representing the table + /** @var The primary index holding all rows. */ + MOT::Index* m_primaryIndex; + + /** @var Number of fields in the table schema. */ + uint32_t m_fieldCnt; + + /** @var Size of raw tuple in bytes. */ + uint32_t m_tupleSize; + + /** @var Maximum number of fields in tuple. */ + uint32_t m_maxFields; + + /** @var The number of secondary indices in use. */ + uint16_t m_numIndexes = 0; + + uint16_t m_filler1; + + /** @var All columns. */ + Column** m_columns = NULL; + + /** @var Secondary index array. */ + MOT::Index** m_indexes = NULL; + + /** @var Current table unique identifier. */ + uint32_t m_tableId = tableCounter++; + + uint32_t m_filler2; + + uint64_t m_tableExId; + + /** @typedef Secondary index map (indexed by index name). */ + typedef std::map SecondaryIndexMap; + + /** @var Secondary index map accessed by name. */ + SecondaryIndexMap m_secondaryIndexes; + + /** @var RW Lock that guards against deletion during checkpoint/vacuum. */ + pthread_rwlock_t m_rwLock; + + /** @var Lock that guards against concurrent DDLs. */ + pthread_mutex_t m_metaLock; + + /** @var metadta version. */ + uint64_t m_metadataVer = 0; + + string m_tableName; + + string m_longTableName; + + /** @var Specifies whether rows have fixed length. */ + bool m_fixedLengthRows = true; + + bool m_deserialized; + + /** @var Holds number of rows in the table. The information may not be accurate. + * Used for execution planning. */ + uint8_t m_filler[6]; + + uint64_t m_filler4; + + uint32_t m_rowCount = 0; + + DECLARE_CLASS_LOGGER(); }; /** @typedef internal table identifier. */ diff --git a/src/gausskernel/storage/mot/core/storage/txn_table.cpp b/src/gausskernel/storage/mot/core/storage/txn_table.cpp new file mode 100644 index 000000000..c8bdc87a6 --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/txn_table.cpp @@ -0,0 +1,860 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * txn_table.cpp + * Wrapper class around Table class to handle DDL operations. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/txn_table.cpp + * + * ------------------------------------------------------------------------- + */ + +#include +#include +#include +#include "txn_table.h" +#include "mot_engine.h" +#include "utilities.h" +#include "txn.h" +#include "txn_access.h" + +namespace MOT { +DECLARE_LOGGER(TxnTable, Storage); + +bool TxnTable::InitTxnTable() +{ + int initRc = pthread_rwlock_init(&m_rwLock, NULL); + if (initRc != 0) { + MOT_LOG_ERROR("failed to initialize TxnTable %s, could not init rwlock (%d)", m_tableName.c_str(), initRc); + return false; + } + initRc = pthread_mutex_init(&m_metaLock, NULL); + if (initRc != 0) { + MOT_LOG_ERROR("failed to initialize Table %s, could not init metalock (%d)", m_tableName.c_str(), initRc); + return false; + } + m_columns = (Column**)memalign(CL_SIZE, m_fieldCnt * sizeof(Column*)); + if (m_columns == nullptr) { + return false; + } + for (uint32_t i = 0; i < m_fieldCnt; i++) { + m_columns[i] = Column::AllocColumn(m_origTab->m_columns[i]->m_type); + if (m_columns[i] == nullptr) { + MOT_LOG_ERROR("failed to allocate column for TxnTable %s", m_tableName.c_str()); + return false; + } + if (m_columns[i]->Clone(m_origTab->m_columns[i]) != RC_OK) { + MOT_LOG_ERROR("failed to clone column for TxnTable %s", m_tableName.c_str()); + return false; + } + } + + m_indexes = (Index**)memalign(CL_SIZE, MAX_NUM_INDEXES * sizeof(Index*)); + if (m_indexes == nullptr) { + return false; + } + for (int i = 0; i < m_origTab->m_numIndexes; i++) { + m_indexes[i] = m_origTab->m_indexes[i]; + } + m_secondaryIndexes.insert(m_origTab->m_secondaryIndexes.begin(), m_origTab->m_secondaryIndexes.end()); + return true; +} + +bool TxnTable::InitRowPool(bool local) +{ + bool result = true; + if (m_rowPool != m_origTab->m_rowPool) { + ObjAllocInterface::FreeObjPool(&m_rowPool); + } + m_rowPool = ObjAllocInterface::GetObjPool(sizeof(Row) + m_tupleSize, local); + if (!m_rowPool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Initialize Table", "Failed to allocate row pool for table %s", m_longTableName.c_str()); + result = false; + } + return result; +} + +bool TxnTable::InitTombStonePool(bool local) +{ + bool result = true; + if (m_tombStonePool != m_origTab->m_tombStonePool) { + ObjAllocInterface::FreeObjPool(&m_tombStonePool); + } + m_tombStonePool = ObjAllocInterface::GetObjPool(sizeof(Row), local); + if (!m_tombStonePool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Initialize Table", "Failed to allocate TS pool for table %s", m_longTableName.c_str()); + result = false; + } + return result; +} + +void TxnTable::ApplyAddIndexFromOtherTxn() +{ + if (m_hasIndexChanges && !m_tooManyIndexes && m_origMetadataVer != m_origTab->m_metadataVer) { + bool localLock = false; + if (!m_isLocked) { + (void)pthread_mutex_lock(&m_origTab->m_metaLock); + localLock = true; + } + for (int i = 1; i < m_origTab->m_numIndexes; i++) { + MOT::Index* ix = m_origTab->m_indexes[i]; + SecondaryIndexMap::iterator it = m_secondaryIndexes.find(ix->GetName()); + if (it == m_secondaryIndexes.end()) { + if (m_numIndexes + 1 > (uint16_t)MAX_NUM_INDEXES) { + m_tooManyIndexes = true; + break; + } else { + m_secondaryIndexes[ix->GetName()] = ix; + // move current txn index + m_indexes[m_numIndexes] = m_indexes[i]; + // set index from other transaction to it's original place + m_indexes[i] = ix; + ++m_numIndexes; + IncIndexColumnUsage(ix); + } + } + } + m_origMetadataVer = m_origTab->m_metadataVer; + if (localLock) { + (void)pthread_mutex_unlock(&m_origTab->m_metaLock); + } + } +} + +RC TxnTable::ValidateDDLChanges(TxnManager* txn) +{ + RC res = RC_OK; + if (m_isDropped) { + return res; + } + m_origTab->WrLock(); + (void)pthread_mutex_lock(&m_origTab->m_metaLock); + m_isLocked = true; + if (m_hasIndexChanges) { + ApplyAddIndexFromOtherTxn(); + if (m_tooManyIndexes) { + res = RC_TABLE_EXCEEDS_MAX_INDEXES; + } + } + return res; +} + +void TxnTable::ApplyDDLChanges(TxnManager* txn) +{ + if (m_isDropped) { + return; + } + // Need to guard critical section + if (!m_isLocked) { + m_origTab->WrLock(); + } + // apply index changes if any + if (m_hasIndexChanges) { + ApplyDDLIndexChanges(txn); + } + // apply column changes + if (m_hasColumnChanges || m_hasColumnRename) { + AlterConvertGlobalData(txn); + for (uint i = 0; i < m_origTab->m_fieldCnt; i++) { + if (m_origTab->m_columns[i] != nullptr) { + delete m_origTab->m_columns[i]; + } + } + + free(m_origTab->m_columns); + m_origTab->m_columns = m_columns; + m_origTab->m_fieldCnt = m_fieldCnt; + m_origTab->m_maxFields = m_maxFields; + m_origTab->m_tupleSize = m_tupleSize; + for (uint i = 0; i < m_origTab->m_fieldCnt; i++) { + m_origTab->m_columns[i]->SetIsCommitted(true); + } + + for (int i = 0; i < m_numIndexes; i++) { + if (!m_origTab->m_indexes[i]->SetNumTableFields(m_origTab->m_fieldCnt)) { + elog(PANIC, "ApplyDDLChanges with alter table operation failed due to insufficient memory"); + } + } + m_columns = nullptr; + } + // apply row pool changes + if (m_rowPool != m_origTab->m_rowPool) { + if (m_origTab->m_rowPool != nullptr) { + ObjAllocInterface::FreeObjPool(&m_origTab->m_rowPool); + } + m_origTab->m_rowPool = m_rowPool; + } + // apply tombstone pool changes + if (m_tombStonePool != m_origTab->m_tombStonePool) { + if (m_origTab->m_tombStonePool != nullptr) { + ObjAllocInterface::FreeObjPool(&m_origTab->m_tombStonePool); + } + m_origTab->m_tombStonePool = m_tombStonePool; + } + m_origTab->m_rowCount = m_rowCount; + m_origTab->m_metadataVer++; + if (m_isLocked) { + (void)pthread_mutex_unlock(&m_origTab->m_metaLock); + m_isLocked = false; + } + m_origTab->Unlock(); +} + +void TxnTable::ApplyDDLIndexChanges(TxnManager* txn) +{ + if (!m_hasIndexChanges) { + return; + } + + // envelope allows concurrent 'create index ...' + // this case handles index addition from multiple connections + if (m_hasOnlyAddIndex) { + for (int i = m_origTab->m_numIndexes; i < m_numIndexes; i++) { + m_indexes[i]->SetTable(m_origTab); + m_indexes[i]->SetSnapshot(txn->GetCommitSequenceNumber()); + m_indexes[i]->SetIsCommited(true); + m_origTab->AddSecondaryIndexToMetaData(m_indexes[i]); + } + } else { + if (m_primaryIndex != m_origTab->m_primaryIndex) { + if (m_origTab->m_primaryIndex != nullptr) { + m_origTab->DeleteIndex(m_origTab->m_primaryIndex); + } + m_origTab->m_primaryIndex = m_primaryIndex; + } + for (int i = 1; i < m_origTab->m_numIndexes; i++) { + SecondaryIndexMap::iterator it = m_secondaryIndexes.find(m_origTab->m_indexes[i]->GetName()); + if (it != m_secondaryIndexes.end()) { + if (it->second != m_origTab->m_indexes[i]) { + m_origTab->DeleteIndex(m_origTab->m_indexes[i]); + } + } else { + m_origTab->DeleteIndex(m_origTab->m_indexes[i]); + } + m_origTab->m_indexes[i] = nullptr; + } + m_origTab->m_secondaryIndexes.clear(); + m_origTab->m_secondaryIndexes.insert(m_secondaryIndexes.begin(), m_secondaryIndexes.end()); + for (int i = 0; i < m_numIndexes; i++) { + m_origTab->m_indexes[i] = m_indexes[i]; + m_indexes[i]->SetTable(m_origTab); + if (m_indexes[i]->GetIsCommited() == false) { + m_indexes[i]->SetSnapshot(txn->GetCommitSequenceNumber()); + } + m_indexes[i]->SetIsCommited(true); + } + m_origTab->m_numIndexes = m_numIndexes; + + // apply column changes caused by indexes + if (!m_hasColumnChanges && !m_hasColumnRename) { + for (uint32_t i = 0; i < m_fieldCnt; i++) { + m_origTab->m_columns[i]->m_numIndexesUsage = m_columns[i]->m_numIndexesUsage; + } + } + } +} + +void TxnTable::RollbackDDLChanges(TxnManager* txn) +{ + // Need to guard critical section + if (!m_isLocked) { + m_origTab->WrLock(); + } + // apply index changes if any + if (m_hasIndexChanges) { + m_secondaryIndexes.clear(); + for (int i = 0; i < m_numIndexes; i++) { + if (!m_indexes[i]->GetIsCommited()) { + DeleteIndex(m_indexes[i]); + } + } + m_numIndexes = 0; + } + // apply column changes + if (m_hasColumnChanges) { + for (uint i = 0; i < m_fieldCnt; i++) { + if (m_columns[i] != nullptr) { + delete m_columns[i]; + m_columns[i] = nullptr; + } + } + } + // apply row pool changes + if (m_rowPool != m_origTab->m_rowPool) { + if (m_rowPool != nullptr) { + ObjAllocInterface::FreeObjPool(&m_rowPool); + } + } + // apply tombstone row pool changes + if (m_tombStonePool != m_origTab->m_tombStonePool) { + if (m_tombStonePool != nullptr) { + ObjAllocInterface::FreeObjPool(&m_tombStonePool); + } + } + if (m_isLocked) { + (void)pthread_mutex_unlock(&m_origTab->m_metaLock); + m_isLocked = false; + } + m_origTab->Unlock(); +} + +RC TxnTable::AlterAddColumn(TxnManager* txn, Column* newColumn) +{ + uint32_t newColCount = m_fieldCnt + 1; + uint64_t newNullBytesSize = BITMAP_GETLEN(newColCount); + bool isNullBytesSizeChanged = (newNullBytesSize > m_columns[0]->m_size ? true : false); + // calculate new row size by adding size of new column and difference of NULLBUTES column size + uint32_t newTupleSize = m_tupleSize + newColumn->m_size + newNullBytesSize - m_columns[0]->m_size; + Column** newColumns = nullptr; + ObjAllocInterface* newRowPool = nullptr; + + RC rc = AlterReserveMem(txn, newTupleSize, newColumn); + if (rc != RC_OK) { + return rc; + } + + MOT_LOG_INFO("Alter table add column memory chunks required %u", m_reservedChunks); + + do { + // reallocate columns + newColumns = (Column**)memalign(CL_SIZE, newColCount * sizeof(Column*)); + if (newColumns == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Alter Add Column", "Failed to allocate columns placeholder"); + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + newRowPool = ObjAllocInterface::GetObjPool(sizeof(Row) + newTupleSize, false); + if (!newRowPool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Alter Add Column", "Failed to allocate row pool for table %s", m_longTableName.c_str()); + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + + // adjust offsets, the null bytes column size may grow only by 1 byte + // so we need to shift all column offsets by one byte + for (uint32_t i = 0; i < m_fieldCnt; i++) { + newColumns[i] = m_columns[i]; + if (isNullBytesSizeChanged) { + if (i == 0) { + newColumns[i]->m_size = newNullBytesSize; + } else { + newColumns[i]->m_offset += 1; + } + } + } + newColumns[m_fieldCnt] = newColumn; + rc = AlterAddConvertTxnData(txn, newColumns, newRowPool, newTupleSize, isNullBytesSizeChanged); + if (rc != RC_OK) { + break; + } + + // from this point failure can not happen + // free old data structures + free(m_columns); + if (m_rowPool != m_origTab->m_rowPool) { + m_rowPool->m_objList = nullptr; + ObjAllocInterface::FreeObjPool(&m_rowPool); + } + // set new data structures + m_columns = newColumns; + m_rowPool = newRowPool; + m_fieldCnt++; + m_maxFields++; + m_tupleSize = newTupleSize; + } while (false); + + // cleanup + if (rc != RC_OK) { + if (newColumns != nullptr) { + delete newColumns; + } + if (newRowPool != nullptr) { + ObjAllocInterface::FreeObjPool(&newRowPool); + } + if (isNullBytesSizeChanged) { + for (uint32_t i = 0; i < m_fieldCnt; i++) { + if (i == 0) { + m_columns[i]->m_size -= 1; + } else { + m_columns[i]->m_offset -= 1; + } + } + } + if (rc == RC_PANIC) { + elog(PANIC, "Alter table operation failed due to insufficient memory"); + } + } + return rc; +} + +RC TxnTable::AlterReserveMem(TxnManager* txn, uint32_t newTupleSize, Column* newColumn) +{ + PoolStatsSt stats; + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + m_rowPool->GetStatsEx(stats, nullptr); + + if (newColumn != nullptr) { + uint64_t actualRecCount = stats.m_totalObjCount - stats.m_freeObjCount; + if (actualRecCount > 0 && newColumn->m_isNotNull && !newColumn->m_hasDefault) { + MOT_LOG_ERROR("NOT Null constraint violation"); + return RC::RC_NULL_VIOLATION; + } + } + + // calculate potential memory required + // if not enough memory available return an error + // reserve memory + size_t required2MBChunks = m_rowPool->CalcRequiredMemDiff(stats, newTupleSize); + if (!m_hasTruncate && m_rowPool != m_origTab->m_rowPool) { + erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + m_origTab->m_rowPool->GetStatsEx(stats, nullptr); + if (newColumn != nullptr) { + uint64_t actualRecCount = stats.m_totalObjCount - stats.m_freeObjCount; + if (actualRecCount > 0 && newColumn->m_isNotNull && !newColumn->m_hasDefault) { + MOT_LOG_ERROR("NOT Null constraint violation"); + return RC::RC_NULL_VIOLATION; + } + } + required2MBChunks += m_origTab->m_rowPool->CalcRequiredMemDiff(stats, newTupleSize); + } + + if (required2MBChunks > m_reservedChunks) { + required2MBChunks -= m_reservedChunks; + if (MemBufferReserveGlobal(required2MBChunks) != 0) { + MOT_LOG_ERROR("Failed to reserve memory for alter table add/drop column"); + return RC_MEMORY_ALLOCATION_ERROR; + } + m_reservedChunks += required2MBChunks; + txn->SetReservedChunks(required2MBChunks); + } else { + required2MBChunks = m_reservedChunks - required2MBChunks; + txn->DecReservedChunks(required2MBChunks); + m_reservedChunks -= required2MBChunks; + } + + return RC_OK; +} + +RC TxnTable::AlterAddConvertTxnData(TxnManager* txn, Column** newColumns, MOT::ObjAllocInterface* newRowPool, + uint32_t newTupleSize, bool nullBitsChanged) +{ + RC rc = RC_OK; + rowAddrMap_t oldToNewMap; + TxnAccess* txnAc = txn->m_accessMgr; + uint64_t oldNullBytesSize = (nullBitsChanged ? newColumns[0]->m_size - 1 : newColumns[0]->m_size); + TxnOrderedSet_t& orderedSet = txnAc->GetOrderedRowSet(); + TxnOrderedSet_t::iterator it = orderedSet.begin(); + while (it != orderedSet.end()) { + Access* ac = it->second; + if (!ac->m_params.IsPrimarySentinel()) { + (void)++it; + continue; + } + if (ac->GetTxnRow()->GetOrigTable() == GetOrigTable()) { + Row** row = nullptr; + switch (ac->m_type) { + case AccessType::RD: + case AccessType::RD_FOR_UPDATE: + case AccessType::DEL: + // Nothing to do + break; + case AccessType::WR: + if (ac->GetLocalVersion() != nullptr) { + if (ac->m_localRow != nullptr && ac->m_localRow->GetOrigTable() == GetOrigTable()) { + rc = txnAc->AddColumn(ac); + if (rc != RC_OK) { + break; + } + row = &ac->m_localRow; + } + } + break; + case AccessType::INS: + if (ac->GetLocalVersion() != nullptr) { + row = &ac->m_localInsertRow; + } + break; + default: + break; + } + if (row != nullptr) { + rc = AlterAddConvertRow(*row, newColumns, newRowPool, nullBitsChanged, oldNullBytesSize, oldToNewMap); + } + } + if (rc != RC_OK) { + break; + } + (void)++it; + } + + // change pointers to a new rows for AC containing secondary sentinels + // this has to be done even in case of error, + // so all pointers will be pointing to a valid memory + AlterConvertSecondaryTxnData(oldToNewMap, txnAc); + return rc; +} + +RC TxnTable::AlterAddConvertRow(Row*& oldRow, Column** newColumns, MOT::ObjAllocInterface* newRowPool, + bool nullBitsChanged, uint64_t oldNullBytesSize, rowAddrMap_t& oldToNewMap) +{ + Row* newRow = newRowPool->Alloc( + *oldRow, nullBitsChanged, oldNullBytesSize, newColumns[0]->m_size, m_tupleSize, newColumns[m_fieldCnt]); + if (newRow == nullptr) { + MOT_LOG_ERROR("Failed to allocate new ObjPool"); + return RC_PANIC; + } + if (newRow->m_pSentinel != nullptr && newRow->m_pSentinel->GetData() == oldRow) { + newRow->m_pSentinel->SetNextPtr(newRow); + } + newRow->SetTable(this); + oldToNewMap[oldRow] = newRow; + m_rowPool->Release(oldRow); + oldRow = newRow; + return RC_OK; +} + +RC TxnTable::AlterDropColumn(TxnManager* txn, Column* col) +{ + uint32_t newTupleSize = m_tupleSize - col->m_size; + ObjAllocInterface* newRowPool = nullptr; + + RC rc = AlterReserveMem(txn, newTupleSize, nullptr); + if (rc != RC_OK) { + return rc; + } + + MOT_LOG_INFO("Alter table drop column memory chunks required %u", m_reservedChunks); + + do { + newRowPool = ObjAllocInterface::GetObjPool(sizeof(Row) + newTupleSize, false); + if (!newRowPool) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Alter Add Column", "Failed to allocate row pool for table %s", m_longTableName.c_str()); + rc = RC_MEMORY_ALLOCATION_ERROR; + break; + } + rc = AlterDropConvertTxnData(txn, col, newRowPool, newTupleSize); + if (rc != RC_OK) { + break; + } + + // from this point failure can not happen + // adjust column offsets after dropped column + for (uint32_t i = col->m_id + 1; i < m_fieldCnt; i++) { + if (!m_columns[i]->m_isDropped) { + m_columns[i]->m_offset -= col->m_size; + } + } + m_columns[col->m_id]->SetDropped(); + // free old data structures + if (m_rowPool != m_origTab->m_rowPool) { + m_rowPool->m_objList = nullptr; + ObjAllocInterface::FreeObjPool(&m_rowPool); + } + // set new metadata structures + m_rowPool = newRowPool; + m_tupleSize = newTupleSize; + } while (false); + + // cleanup + if (rc != RC_OK) { + if (newRowPool != nullptr) { + ObjAllocInterface::FreeObjPool(&newRowPool); + } + rc = RC_PANIC; + elog(PANIC, "Alter table operation failed due to insufficient memory"); + } + return rc; +} + +RC TxnTable::AlterDropConvertTxnData( + TxnManager* txn, Column* col, MOT::ObjAllocInterface* newRowPool, uint32_t newTupleSize) +{ + RC res = RC_OK; + rowAddrMap_t oldToNewMap; + size_t offset1 = sizeof(Row) + col->m_offset; + size_t offset2 = sizeof(Row) + col->m_offset + col->m_size; + size_t len2 = sizeof(Row) + m_tupleSize - offset2; + TxnAccess* txnAc = txn->m_accessMgr; + TxnOrderedSet_t& orderedSet = txnAc->GetOrderedRowSet(); + TxnOrderedSet_t::iterator itr = orderedSet.begin(); + while (itr != orderedSet.end() && res == RC_OK) { + Access* access = itr->second; + if (!access->m_params.IsPrimarySentinel()) { + (void)++itr; + continue; + } + if (access->GetTxnRow()->GetOrigTable() == GetOrigTable()) { + Row** row = nullptr; + switch (access->m_type) { + case AccessType::RD: + case AccessType::RD_FOR_UPDATE: + case AccessType::DEL: + // Nothing to do + break; + case AccessType::WR: + if (access->GetLocalVersion() != nullptr) { + if (access->m_localRow != nullptr && access->m_localRow->GetOrigTable() == GetOrigTable()) { + txnAc->DropColumn(access, col); + row = &access->m_localRow; + } + } + break; + case AccessType::INS: + if (access->GetLocalVersion() != nullptr) { + row = &access->m_localInsertRow; + } + break; + default: + break; + } + if (row != nullptr) { + res = AlterDropConvertRow(*row, newRowPool, col, offset1, offset2, len2, oldToNewMap); + } + } + if (res != RC_OK) { + break; + } + (void)++itr; + } + + // change pointers to a new rows for AC containing secondary sentinels + AlterConvertSecondaryTxnData(oldToNewMap, txnAc); + return res; +} + +RC TxnTable::AlterDropConvertRow(Row*& oldRow, MOT::ObjAllocInterface* newRowPool, Column* col, size_t offset1, + size_t offset2, size_t len2, rowAddrMap_t& oldToNewMap) +{ + Row* newRow = newRowPool->Alloc(this); + if (newRow == nullptr) { + MOT_LOG_ERROR("Failed to allocate new ObjPool"); + return RC_PANIC; + } + // copy including row header until dropped column + errno_t erc = memcpy_s(static_cast(newRow), offset1, static_cast(oldRow), offset1); + securec_check(erc, "\0", "\0"); + // copy data after dropped column + if (len2 > 0) { + erc = memcpy_s(((uint8_t*)newRow) + offset1, len2, ((uint8_t*)oldRow) + offset2, len2); + securec_check(erc, "\0", "\0"); + } + if (newRow->m_pSentinel != nullptr && newRow->m_pSentinel->GetData() == oldRow) { + newRow->m_pSentinel->SetNextPtr(newRow); + } + uint8_t* data = (uint8_t*)newRow->GetData(); + BITMAP_CLEAR(data, col->m_id - 1); + newRow->SetTable(this); + oldToNewMap[oldRow] = newRow; + m_rowPool->Release(oldRow); + oldRow = newRow; + return RC_OK; +} + +void TxnTable::AlterConvertSecondaryTxnData(rowAddrMap_t& oldToNewMap, TxnAccess* txnAc) +{ + TxnOrderedSet_t& orderedSet = txnAc->GetOrderedRowSet(); + // change pointers to a new rows for AC containing secondary sentinels + for (TxnOrderedSet_t::iterator it = orderedSet.begin(); it != orderedSet.end(); (void)++it) { + Access* ac = it->second; + if (ac->m_type == AccessType::INS && !ac->m_params.IsPrimarySentinel()) { + rowAddrMap_t::iterator itr = oldToNewMap.find(ac->m_localInsertRow); + if (itr != oldToNewMap.end()) { + ac->m_localInsertRow = itr->second; + } + } + } +} + +void TxnTable::AlterConvertGlobalData(TxnManager* txn) +{ + if (!m_hasColumnChanges || m_hasTruncate) { + return; + } + + // clean all GC elements for the table, this call should actually release + // all elements into an appropriate object pool + for (uint16_t i = 0; i < m_origTab->GetNumIndexes(); i++) { + MOT::Index* index = m_origTab->GetIndex(i); + GcManager::ClearIndexElements(index->GetIndexId(), GC_OPERATION_TYPE::GC_ALTER_TABLE); + } + + RC rc = RC_OK; + uint32_t count = 0; + ObjPool* oldCurr = nullptr; + PoolStatsSt stats; + ObjPoolAddrSet_t poolSet; + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + m_origTab->m_rowPool->GetStatsEx(stats, &poolSet); + ObjPoolAddrSet_t::iterator it = poolSet.begin(); + + m_origTab->m_rowPool->ClearAllThreadCache(); + acRowAddrMap_t oldToNewMap; + TxnAccess* txnAc = txn->m_accessMgr; + TxnOrderedSet_t& orderedSet = txnAc->GetOrderedRowSet(); + TxnOrderedSet_t::iterator oit = orderedSet.begin(); + while (oit != orderedSet.end()) { + Access* ac = oit->second; + if (!ac->m_params.IsPrimarySentinel() && ac->m_localInsertRow != nullptr && + ac->m_localInsertRow->GetOrigTable() == m_origTab) { + (void)oldToNewMap.insert({ac->m_localInsertRow, ac}); + } + (void)++oit; + } + // NOTE: there should not be any transactional data at this stage + while (it != poolSet.end()) { + oldCurr = *it; + Row* oldRow; + ObjPoolItr itr(oldCurr); + while ((oldRow = (Row*)itr.Next()) != nullptr) { + rc = AlterConvertGlobalRow(oldRow, oldToNewMap); + if (rc != RC_OK) { + break; + } + // release old object + itr.ReleaseCurrent(); + count++; + } + if (rc != RC_OK) { + break; + } + // free empty object pool + ObjPool::DelObjPool(oldCurr, m_origTab->m_rowPool->m_type, m_origTab->m_rowPool->m_global); + (void)++it; + } + // Convert global rows pointers in transactional data + oit = orderedSet.begin(); + while (oit != orderedSet.end()) { + Access* ac = oit->second; + if (ac->m_params.IsPrimarySentinel()) { + if (ac->m_type == WR or ac->m_type == DEL or ac->m_params.IsUpgradeInsert()) { + ac->m_globalRow = ac->m_origSentinel->GetData(); + } + } + (void)++oit; + } + if (rc == RC_OK) { + MOT_LOG_INFO("Rows converted %u", count); + m_origTab->m_rowPool->m_objList = nullptr; + } else { + elog(PANIC, "Alter table operation failed due to insufficient memory"); + } +} + +RC TxnTable::AlterConvertGlobalRow(Row* oldRow, acRowAddrMap_t& oldToNewMap) +{ + Row* newRow = + (Row*)m_rowPool->Alloc(*oldRow, m_columns, m_fieldCnt, m_origTab->m_columns, m_origTab->m_fieldCnt); + if (newRow == nullptr) { + MOT_LOG_ERROR("Failed to allocate new ObjPool"); + // non-recoverable error + // should not happen, see required memory calculations + return RC_PANIC; + } + + MOT_ASSERT(newRow->GetNextVersion() == nullptr); + + // assign row to a sentinel + if (newRow->m_pSentinel != nullptr) { + Row* t = newRow->m_pSentinel->GetData(); + if (t != nullptr) { + if (t->IsRowDeleted()) { + if (t->GetNextVersion() == oldRow) { + t->SetNextVersion(newRow); + } else { + MOT_ASSERT(false); + } + } else if (t == oldRow) { + MOT_ASSERT(t->GetNextVersion() == nullptr); + newRow->m_pSentinel->SetNextPtr(newRow); + } else { + MOT_ASSERT(false); + } + } + } + + newRow->SetTable(m_origTab); + auto nit = oldToNewMap.equal_range(oldRow); + for (auto nitR = nit.first; nitR != nit.second; ++nitR) { + nitR->second->m_localInsertRow = newRow; + } + + return RC_OK; +} + +RC TxnTable::AlterRenameColumn(TxnManager* txn, Column* col, char* newname, DDLAlterTableRenameColumn* alter) +{ + RC rc = RC_OK; + Column* txnCol = m_columns[col->m_id]; + MOT_LOG_INFO("Alter table rename column %s to %s", col->m_name, newname); + size_t len = strlen(newname); + errno_t erc = memset_s(txnCol->m_name, sizeof(txnCol->m_name), 0, sizeof(txnCol->m_name)); + securec_check(erc, "\0", "\0"); + erc = memcpy_s(txnCol->m_name, Column::MAX_COLUMN_NAME_LEN, newname, len); + securec_check(erc, "\0", "\0"); + txnCol->m_nameLen = len; + // copy names for redo log operations + erc = memcpy_s(alter->m_newname, Column::MAX_COLUMN_NAME_LEN, newname, len); + securec_check(erc, "\0", "\0"); + alter->m_newname[len] = 0; + erc = memcpy_s(alter->m_oldname, Column::MAX_COLUMN_NAME_LEN, col->m_name, col->m_nameLen); + securec_check(erc, "\0", "\0"); + alter->m_oldname[col->m_nameLen] = 0; + return rc; +} + +TxnTable::~TxnTable() +{ + if (m_columns != nullptr) { + for (uint i = 0; i < m_fieldCnt; i++) { + if (m_columns[i] != nullptr) { + delete m_columns[i]; + m_columns[i] = nullptr; + } + } + free(m_columns); + m_columns = nullptr; + } + if (m_indexes != nullptr) { + free(m_indexes); + m_indexes = nullptr; + } + m_rowPool = nullptr; + m_tombStonePool = nullptr; + m_primaryIndex = nullptr; + m_secondaryIndexes.clear(); + m_numIndexes = 0; + int destroyRc = pthread_rwlock_destroy(&m_rwLock); + if (destroyRc != 0) { + MOT_LOG_ERROR("~TxnTable: rwlock destroy failed (%d)", destroyRc); + } + destroyRc = pthread_mutex_destroy(&m_metaLock); + if (destroyRc != 0) { + MOT_LOG_ERROR("~Table: metaLock destroy failed (%d)", destroyRc); + } + + m_origTab = nullptr; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/storage/txn_table.h b/src/gausskernel/storage/mot/core/storage/txn_table.h new file mode 100644 index 000000000..a08fea1ab --- /dev/null +++ b/src/gausskernel/storage/mot/core/storage/txn_table.h @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * txn_table.h + * Wrapper class around Table class to handle DDL operations. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/storage/txn_table.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef TXN_TABLE_H +#define TXN_TABLE_H + +#include "table.h" + +namespace MOT { +class DDLAlterTableAddDropColumn { +public: + DDLAlterTableAddDropColumn() : m_table(nullptr), m_column(nullptr) + {} + ~DDLAlterTableAddDropColumn() + { + m_table = nullptr; + m_column = nullptr; + } + + TxnTable* m_table; + Column* m_column; +}; + +class DDLAlterTableRenameColumn { +public: + DDLAlterTableRenameColumn() : m_table(nullptr) + {} + + ~DDLAlterTableRenameColumn() + { + m_table = nullptr; + } + + TxnTable* m_table; + char m_oldname[Column::MAX_COLUMN_NAME_LEN]; + char m_newname[Column::MAX_COLUMN_NAME_LEN]; +}; + +class alignas(CL_SIZE) TxnTable : public Table { + friend Row; + +public: + explicit TxnTable(Table* table) + : m_origTab(table), + m_origMetadataVer(table->m_metadataVer), + m_tooManyIndexes(false), + m_hasColumnChanges(false), + m_hasColumnRename(false), + m_hasIndexChanges(false), + m_hasOnlyAddIndex(true), // the default is true to differentiate first drop index + m_hasTruncate(false), + m_isDropped(false), + m_isNew(false), + m_isLocked(false), + m_reservedChunks(0) + { + m_deserialized = table->m_deserialized; + m_fieldCnt = table->m_fieldCnt; + m_fixedLengthRows = table->m_fixedLengthRows; + m_longTableName = table->m_longTableName; + m_maxFields = table->m_maxFields; + m_numIndexes = table->m_numIndexes; + m_primaryIndex = table->m_primaryIndex; + m_rowCount = table->m_rowCount; + m_rowPool = table->m_rowPool; + m_tombStonePool = table->m_tombStonePool; + m_tableExId = table->m_tableExId; + m_tableId = table->m_tableId; + m_tableName = table->m_tableName; + m_tupleSize = table->m_tupleSize; + } + + ~TxnTable() override; + bool IsTxnTable() override + { + return true; + } + Table* GetOrigTable() override + { + return m_origTab; + } + void SetHasColumnChanges() + { + m_hasColumnChanges = true; + m_hasOnlyAddIndex = false; + } + void SetHasColumnRename() + { + m_hasColumnRename = true; + m_hasOnlyAddIndex = false; + } + void SetHasIndexChanges(bool isDrop) + { + if (isDrop && m_hasOnlyAddIndex) { + m_hasOnlyAddIndex = false; + } + m_hasIndexChanges = true; + } + void SetHasTruncate() + { + m_hasTruncate = true; + m_hasIndexChanges = true; + m_hasOnlyAddIndex = false; + (void)MemBufferUnreserveGlobal(m_reservedChunks); + m_reservedChunks = 0; + } + bool IsHasTruncate() const + { + return m_hasTruncate; + } + bool GetHasColumnChanges() const override + { + return m_hasColumnChanges; + } + bool IsDropped() const + { + return m_isDropped; + } + void SetIsDropped() + { + m_isDropped = true; + } + void SetIsNew() + { + m_isNew = true; + } + bool GetIsNew() const + { + return m_isNew; + } + + /** + * @brief Remove Index from table meta data. + * @param index The index to use. + * @return void. + */ + void RemoveSecondaryIndexFromMetaData(MOT::Index* index) + { + if (!index->IsPrimaryKey()) { + uint16_t rmIx = 0; + for (uint16_t i = 1; i < m_numIndexes; i++) { + if (m_indexes[i] == index) { + rmIx = i; + break; + } + } + + // prevent removing primary by mistake + if (rmIx > 0) { + DecIndexColumnUsage(index); + m_numIndexes--; + for (uint16_t i = rmIx; i < m_numIndexes; i++) { + m_indexes[i] = m_indexes[i + 1]; + } + + (void)m_secondaryIndexes.erase(index->GetName()); + m_indexes[m_numIndexes] = nullptr; + } + } + } + + bool InitTxnTable(); + bool InitRowPool(bool local) override; + bool InitTombStonePool(bool local) override; + void ApplyDDLChanges(TxnManager* txn); + void RollbackDDLChanges(TxnManager* txn); + RC ValidateDDLChanges(TxnManager* txn); + RC AlterAddColumn(TxnManager* txn, Column* newColumn); + RC AlterDropColumn(TxnManager* txn, Column* col); + RC AlterRenameColumn(TxnManager* txn, Column* col, char* newname, DDLAlterTableRenameColumn* alter); + void ApplyAddIndexFromOtherTxn(); + +private: + RC AlterAddConvertRow(Row*& oldRow, Column** newColumns, MOT::ObjAllocInterface* newRowPool, bool nullBitsChanged, + uint64_t oldNullBytesSize, rowAddrMap_t& oldToNewMap); + RC AlterDropConvertRow(Row*& oldRow, MOT::ObjAllocInterface* newRowPool, Column* col, size_t offset1, + size_t offset2, size_t len2, rowAddrMap_t& oldToNewMap); + void AlterConvertSecondaryTxnData(rowAddrMap_t& oldToNewMap, TxnAccess* txnAc); + RC AlterAddConvertTxnData(TxnManager* txn, Column** newColumns, MOT::ObjAllocInterface* newRowPool, + uint32_t newTupleSize, bool nullBitsChanged); + RC AlterDropConvertTxnData(TxnManager* txn, Column* col, MOT::ObjAllocInterface* newRowPool, uint32_t newTupleSize); + void ApplyDDLIndexChanges(TxnManager* txn); + void AlterConvertGlobalData(TxnManager* txn); + RC AlterConvertGlobalRow(Row* oldRow, acRowAddrMap_t& oldToNewMap); + RC AlterReserveMem(TxnManager* txn, uint32_t newTupleSize, Column* newColumn); + + Table* m_origTab; + uint64_t m_origMetadataVer; + bool m_tooManyIndexes; + bool m_hasColumnChanges; + bool m_hasColumnRename; + bool m_hasIndexChanges; + bool m_hasOnlyAddIndex; + bool m_hasTruncate; + bool m_isDropped; + bool m_isNew; + bool m_isLocked; + size_t m_reservedChunks; +}; +} // namespace MOT + +#endif /* TXN_TABLE_H */ diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.cpp b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.cpp index aaab38ed6..76e5794fc 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.cpp +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.cpp @@ -42,7 +42,7 @@ CheckpointControlFile* CheckpointControlFile::GetCtrlFile() return ctrlfileInst; } - g_ctrlfileLock.lock(); + std::lock_guard lock(g_ctrlfileLock); ctrlfileInst = new (std::nothrow) CheckpointControlFile(); if (ctrlfileInst == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Checkpoint", "Failed to allocate memory for checkpoint control file object"); @@ -54,7 +54,6 @@ CheckpointControlFile* CheckpointControlFile::GetCtrlFile() ctrlfileInst = nullptr; } } - g_ctrlfileLock.unlock(); return ctrlfileInst; } @@ -65,7 +64,7 @@ bool CheckpointControlFile::Init() } do { - if (GetGlobalConfiguration().m_checkpointDir.length() >= CheckpointUtils::maxPath) { + if (GetGlobalConfiguration().m_checkpointDir.length() >= CheckpointUtils::MAX_PATH) { MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, "Checkpoint", "Invalid checkpoint_dir configuration, length exceeds max path length"); @@ -84,7 +83,7 @@ bool CheckpointControlFile::Init() } // "/" is already appended in CheckpointUtils::GetWorkingDir. - m_fullPath.append(CTRL_FILE_NAME); + (void)m_fullPath.append(CTRL_FILE_NAME); MOT_LOG_TRACE("CheckpointControlFile: Fullpath - '%s'", m_fullPath.c_str()); // try to open an old file @@ -99,7 +98,7 @@ bool CheckpointControlFile::Init() } if (CheckpointUtils::ReadFile(fd, (char*)&m_ctrlFileData, sizeof(CtrlFileData)) != sizeof(CtrlFileData)) { MOT_LOG_ERROR("CheckpointControlFile: init - failed to read data from file"); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); break; } else { MOT_LOG_INFO("CheckpointControlFile: init - loaded file: checkpointId %lu", @@ -118,7 +117,7 @@ bool CheckpointControlFile::Init() return initialized; } -bool CheckpointControlFile::Update(uint64_t id, uint64_t lsn, uint64_t lastReplayLsn) +bool CheckpointControlFile::Update(uint64_t id, uint64_t lsn, uint64_t lastReplayLsn, uint64_t maxTxnId) { int fd = -1; bool ret = false; @@ -129,20 +128,23 @@ bool CheckpointControlFile::Update(uint64_t id, uint64_t lsn, uint64_t lastRepla break; } + uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; m_ctrlFileData.entry[0].checkpointId = id; m_ctrlFileData.entry[0].lsn = lsn; m_ctrlFileData.entry[0].lastReplayLsn = lastReplayLsn; - m_ctrlFileData.entry[0].metaVersion = MetadataProtoVersion::METADATA_VER_CURR; + m_ctrlFileData.entry[0].metaVersion = metaVersion; + m_ctrlFileData.entry[0].maxTransactionId = maxTxnId; - if (CheckpointUtils::WriteFile(fd, (char*)&m_ctrlFileData, sizeof(CtrlFileData)) != sizeof(CtrlFileData)) { + if (CheckpointUtils::WriteFile(fd, (const char*)&m_ctrlFileData, sizeof(CtrlFileData)) != + sizeof(CtrlFileData)) { MOT_LOG_ERROR("CheckpointControlFile::update - failed to write control file"); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); break; } if (CheckpointUtils::FlushFile(fd)) { MOT_LOG_ERROR("CheckpointControlFile::update - failed to flush control file"); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); break; } @@ -151,14 +153,14 @@ bool CheckpointControlFile::Update(uint64_t id, uint64_t lsn, uint64_t lastRepla break; } ret = true; - MOT_LOG_DEBUG("CheckpointControlFile::Update %lu:%lu", id, lsn); + MOT_LOG_TRACE("CheckpointControlFile::Update [%lu:%lu:%lu:%u]", id, lsn, lastReplayLsn, metaVersion); } while (0); return ret; } -void CheckpointControlFile::Print() +void CheckpointControlFile::Print() const { - MOT_LOG_DEBUG("CheckpointControlFile: [%lu:%lu:%lu]", GetLsn(), GetId(), GetLastReplayLsn()); + MOT_LOG_DEBUG("CheckpointControlFile: [%lu:%lu:%lu:%u]", GetId(), GetLsn(), GetLastReplayLsn(), GetMetaVersion()); } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.h b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.h index 9f1e6f90d..5569167ae 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.h +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_ctrlfile.h @@ -24,6 +24,7 @@ #ifndef CHECKPOINT_CTRLFILE_H #define CHECKPOINT_CTRLFILE_H + #include "table.h" namespace MOT { @@ -44,7 +45,7 @@ public: bool Init(); struct CtrlFileElem { - CtrlFileElem(uint64_t id = invalidId, uint64_t lsn = invalidId, uint64_t replay = invalidId, + explicit CtrlFileElem(uint64_t id = INVALID_ID, uint64_t lsn = INVALID_ID, uint64_t replay = INVALID_ID, uint64_t maxTxnId = 0, uint32_t ver = MetadataProtoVersion::METADATA_VER_CURR) : checkpointId(id), lsn(lsn), @@ -56,9 +57,9 @@ public: void Init() { - checkpointId = invalidId; - lsn = invalidId; - lastReplayLsn = invalidId; + checkpointId = INVALID_ID; + lsn = INVALID_ID; + lastReplayLsn = INVALID_ID; maxTransactionId = 0; metaVersion = MetadataProtoVersion::METADATA_VER_CURR; padding = 0; @@ -94,21 +95,26 @@ public: return m_ctrlFileData.entry[0].metaVersion; } + uint64_t GetMaxTransactionId() const + { + return m_ctrlFileData.entry[0].maxTransactionId; + } + /** * @brief Performs a durable update of the checkpoint id in the file * @param id The checkpoint's id. * @return Boolean value denoting success or failure. */ - bool Update(uint64_t id, uint64_t lsn, uint64_t lastReplayLsn); + bool Update(uint64_t id, uint64_t lsn, uint64_t lastReplayLsn, uint64_t maxTxnId); bool IsValid() const { return m_valid; } - void Print(); + void Print() const; - static const uint64_t invalidId = (uint64_t)(-1); + static const uint64_t INVALID_ID = (uint64_t)(-1); static constexpr const char* CTRL_FILE_NAME = "mot.ctrl"; @@ -130,8 +136,6 @@ private: static CheckpointControlFile* ctrlfileInst; - const char* m_defaultDir = "/tmp"; - std::string m_fullPath; struct CtrlFileData m_ctrlFileData; diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.cpp b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.cpp index d02ed13a9..18b39c1b9 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.cpp +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.cpp @@ -48,15 +48,15 @@ CheckpointManager::CheckpointManager() m_numCpTasks(0), m_numThreads(GetGlobalConfiguration().m_checkpointWorkers), m_cpSegThreshold(GetGlobalConfiguration().m_checkpointSegThreshold), - m_stopFlag(false), m_checkpointEnded(false), m_checkpointError(0), m_errorSet(false), m_counters{{0}}, m_lsn(0), - m_id(CheckpointControlFile::invalidId), - m_inProgressId(CheckpointControlFile::invalidId), + m_id(CheckpointControlFile::INVALID_ID), + m_inProgressId(CheckpointControlFile::INVALID_ID), m_lastReplayLsn(0), + m_workingDir(""), m_inProcessTxnsLsn(0), m_numSerializedEntries(0) {} @@ -68,38 +68,60 @@ bool CheckpointManager::Initialize() MOT_LOG_ERROR("Failed to initialize CheckpointManager, could not init rwlock (%d)", initRc); return false; } + if (GetGlobalConfiguration().m_enableCheckpoint) { + m_checkpointers = new (std::nothrow) CheckpointWorkerPool(*this, m_cpSegThreshold); + if (m_checkpointers == nullptr) { + MOT_LOG_ERROR("Failed to allocate CheckpointWorkerPool"); + return false; + } + if (!m_checkpointers->Start()) { + MOT_LOG_ERROR("Failed to spawn checkpoint worker threads"); + return false; + } + } return true; } void CheckpointManager::ResetFlags() { m_checkpointEnded = false; - m_stopFlag = false; m_errorSet = false; } CheckpointManager::~CheckpointManager() { - if (m_checkpointers != nullptr) { - delete m_checkpointers; - m_checkpointers = nullptr; - } + DestroyCheckpointers(); (void)pthread_rwlock_destroy(&m_fetchLock); + m_redoLogHandler = nullptr; } bool CheckpointManager::CreateSnapShot() { if (!CheckpointManager::CreateCheckpointId(m_inProgressId)) { MOT_LOG_ERROR("Could not begin checkpoint, checkpoint id creation failed"); - OnError(CheckpointWorkerPool::ErrCodes::CALC, "Could not begin checkpoint, checkpoint id creation failed"); + OnError( + CheckpointWorkerPool::ErrCodes::CALC, "Could not begin checkpoint, checkpoint id creation failed", nullptr); return false; } MOT_LOG_INFO("Creating MOT checkpoint snapshot: id: %lu", m_inProgressId); if (m_phase != CheckpointPhase::REST) { MOT_LOG_ERROR("Could not begin checkpoint, checkpoint is already running"); - OnError(CheckpointWorkerPool::ErrCodes::CALC, "Could not begin checkpoint, checkpoint is already running"); + OnError( + CheckpointWorkerPool::ErrCodes::CALC, "Could not begin checkpoint, checkpoint is already running", nullptr); + return false; + } + + if (!CheckpointUtils::SetWorkingDir(m_workingDir, m_inProgressId)) { + MOT_LOG_ERROR("failed to setup working dir"); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "failed to setup working dir", nullptr); + return false; + } + + if (!CheckpointManager::CreateCheckpointDir(m_workingDir)) { + MOT_LOG_ERROR("failed to create working dir: %s", m_workingDir.c_str()); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "failed to create working dir", m_workingDir.c_str()); return false; } @@ -115,23 +137,25 @@ bool CheckpointManager::CreateSnapShot() m_lock.WrUnlock(); while (m_phase != CheckpointPhase::RESOLVE) { - usleep(50000L); + (void)usleep(50000L); } // Ensure that all the transactions that started commit in PREPARE phase are completed. WaitPrevPhaseCommittedTxnComplete(); + // Lock the recovery manager from obtaining any more segments (relevant to standby only) + // Flush will be a no-op on the primary + GetRecoveryManager()->Lock(); + GetRecoveryManager()->Flush(); + // Now in RESOLVE phase, no transaction is allowed to start the commit. // It is safe now to obtain a list of all tables to included in this checkpoint. // The tables are read locked in order to avoid drop/truncate during checkpoint. FillTasksQueue(); - if (!CreateCheckpointDir()) { - return false; - } - - if (!CreateTpcRecoveryFile()) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create tpc recovery file"); + if (!CreatePendingRecoveryDataFile()) { + MOT_LOG_ERROR("Failed to create the pending recovery data file"); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create the pending recovery data file", nullptr); return false; } @@ -145,7 +169,7 @@ bool CheckpointManager::CreateSnapShot() bool CheckpointManager::SnapshotReady(uint64_t lsn) { - MOT_LOG_INFO("MOT snapshot ready. id: %lu, lsn: %lu", m_inProgressId, m_lsn); + MOT_LOG_INFO("MOT checkpoint snapshot ready [%lu:%lu]", m_inProgressId, lsn); if (m_phase != CheckpointPhase::CAPTURE) { MOT_LOG_ERROR("BAD Checkpoint state. Checkpoint ID: %lu, expected: 'CAPTURE', actual: %s", m_inProgressId, @@ -153,8 +177,11 @@ bool CheckpointManager::SnapshotReady(uint64_t lsn) m_errorSet = true; } else { SetLsn(lsn); - if (m_redoLogHandler != nullptr) + if (m_redoLogHandler != nullptr) { m_redoLogHandler->WrUnlock(); + } + // release the recovery manager's lock + GetRecoveryManager()->Unlock(); MOT_LOG_DEBUG("Checkpoint snapshot ready. Checkpoint ID: %lu, LSN: %lu", m_inProgressId, GetLsn()); } return !m_errorSet; @@ -164,8 +191,9 @@ bool CheckpointManager::BeginCheckpoint() { MOT_LOG_INFO("MOT begin checkpoint capture. id: %lu, lsn: %lu", m_inProgressId, m_lsn); Capture(); + while (!m_checkpointEnded) { - usleep(100000L); + (void)usleep(100000L); if (m_finishedTasks.empty() == false) { std::lock_guard guard(m_tasksMutex); UnlockAndClearTables(m_finishedTasks); @@ -253,15 +281,16 @@ void CheckpointManager::BeginCommit(TxnManager* txn) // want them to be written to the redo-log before we take the // redo-log LSN (redo-point). The redo-point will be taken by the // envelope after we reach the CAPTURE phase. + txn->SetCheckpointCommitEnded(false); m_lock.RdLock(); while (!MOTEngine::GetInstance()->IsRecovering() && m_phase == CheckpointPhase::RESOLVE) { m_lock.RdUnlock(); - usleep(5000); + (void)usleep(5000); m_lock.RdLock(); } txn->m_checkpointPhase = m_phase; txn->m_checkpointNABit = !m_availableBit; - m_counters[m_cntBit].fetch_add(1); + (void)m_counters[m_cntBit].fetch_add(1); m_lock.RdUnlock(); } @@ -271,11 +300,11 @@ void CheckpointManager::FreePreAllocStableRows(TxnManager* txn) const Access* access = nullptr; for (const auto& ra_pair : orderedSet) { access = ra_pair.second; - if (access->m_type == RD || access->m_type == INS) { + if (access->m_type == RD || (access->m_type == INS && access->m_params.IsUpgradeInsert() == false)) { continue; } if (access->m_params.IsPrimarySentinel()) { - Sentinel* s = access->GetRowFromHeader()->GetPrimarySentinel(); + PrimarySentinel* s = access->GetRowFromHeader()->GetPrimarySentinel(); MOT_ASSERT(s != nullptr); if (s->GetStablePreAllocStatus()) { CheckpointUtils::DestroyStableRow(s->GetStable()); @@ -292,20 +321,20 @@ void CheckpointManager::EndCommit(TxnManager* txn) return; } + txn->SetCheckpointCommitEnded(true); m_lock.RdLock(); CheckpointPhase current_phase = m_phase; if (txn->m_checkpointPhase == m_phase) { // current phase - m_counters[m_cntBit].fetch_sub(1); + (void)m_counters[m_cntBit].fetch_sub(1); } else { // previous phase - m_counters[!m_cntBit].fetch_sub(1); + (void)m_counters[!m_cntBit].fetch_sub(1); } - if (txn->m_replayLsn != 0 && MOTEngine::GetInstance()->IsRecovering()) { + if (txn->GetReplayLsn() != 0 && MOTEngine::GetInstance()->IsRecovering()) { // Update the last replay LSN in recovery manager in case of redo replay. // This is needed for getting the last replay LSN during checkpoint in standby. GetRecoveryManager()->SetLastReplayLsn(txn->GetReplayLsn()); } m_lock.RdUnlock(); - if (m_counters[!m_cntBit] == 0 && IsAutoCompletePhase()) { m_lock.WrLock(); // If the state was not change by another thread in the meanwhile, @@ -322,7 +351,7 @@ void CheckpointManager::WaitPrevPhaseCommittedTxnComplete() m_lock.RdLock(); while (m_counters[!m_cntBit] != 0) { m_lock.RdUnlock(); - usleep(10000); + (void)usleep(10000); m_lock.RdLock(); } m_lock.RdUnlock(); @@ -363,7 +392,10 @@ void CheckpointManager::MoveToNextPhase() // Get the current last replay LSN from recovery manager and use it as m_lastReplayLsn // for the current checkpoint. If the system recovers from disk after this checkpoint, // it is safe to ignore any redo replay before this LSN. - SetLastReplayLsn(std::max(m_inProcessTxnsLsn, GetRecoveryManager()->GetLastReplayLsn())); + SetLastReplayLsn(GetRecoveryManager()->GetLastReplayLsn()); + MOT_LOG_TRACE("MOT checkpoint setting lastReplayLsn as %lu (standby)", m_lastReplayLsn); + } else { + MOT_LOG_TRACE("MOT checkpoint setting lastReplayLsn as %lu (primary)", m_lastReplayLsn); } } @@ -388,6 +420,8 @@ const char* CheckpointManager::PhaseToString(CheckpointPhase phase) return "CAPTURE"; case COMPLETE: return "COMPLETE"; + default: + break; } return "UNKNOWN"; } @@ -396,11 +430,13 @@ bool CheckpointManager::PreAllocStableRow(TxnManager* txnMan, Row* origRow, Acce { CheckpointPhase startPhase = txnMan->m_checkpointPhase; MOT_ASSERT(startPhase != RESOLVE || MOTEngine::GetInstance()->IsRecovering()); - Sentinel* s = origRow->GetPrimarySentinel(); + PrimarySentinel* s = static_cast(origRow->GetPrimarySentinel()); MOT_ASSERT(s != nullptr); - + if (origRow->GetRowType() == RowType::TOMBSTONE) { + return true; + } bool statusBit = s->GetStableStatus(); - if (startPhase == CAPTURE && type != INS && statusBit == !m_availableBit) { + if (startPhase == CAPTURE && statusBit == !m_availableBit) { MOT_ASSERT(s->GetStablePreAllocStatus() == false); if (!CheckpointUtils::CreateStableRow(origRow)) { MOT_LOG_ERROR("Failed to create stable row"); @@ -412,11 +448,12 @@ bool CheckpointManager::PreAllocStableRow(TxnManager* txnMan, Row* origRow, Acce return true; } -void CheckpointManager::ApplyWrite(TxnManager* txnMan, Row* origRow, AccessType type) +void CheckpointManager::ApplyWrite(TxnManager* txnMan, Row* origRow, const Access* access) { + AccessType type = access->m_type; CheckpointPhase startPhase = txnMan->m_checkpointPhase; MOT_ASSERT(startPhase != RESOLVE || MOTEngine::GetInstance()->IsRecovering()); - Sentinel* s = origRow->GetPrimarySentinel(); + PrimarySentinel* s = static_cast(origRow->GetPrimarySentinel()); MOT_ASSERT(s != nullptr); bool statusBit = s->GetStableStatus(); @@ -440,7 +477,7 @@ void CheckpointManager::ApplyWrite(TxnManager* txnMan, Row* origRow, AccessType } break; case CAPTURE: - if (type == INS) { + if (type == INS && (access->m_params.IsUpgradeInsert() == false || origRow->IsRowDeleted())) { s->SetStableStatus(m_availableBit); } else { if (statusBit == !m_availableBit) { @@ -465,7 +502,8 @@ void CheckpointManager::FillTasksQueue() { if (!m_tasksList.empty()) { MOT_LOG_ERROR("CheckpointManager::fillTasksQueue: queue is not empty!"); - OnError(CheckpointWorkerPool::ErrCodes::CALC, "CheckpointManager::fillTasksQueue: queue is not empty!"); + OnError( + CheckpointWorkerPool::ErrCodes::CALC, "CheckpointManager::fillTasksQueue: queue is not empty!", nullptr); return; } GetTableManager()->AddTablesToList(m_tasksList); @@ -477,7 +515,7 @@ void CheckpointManager::FillTasksQueue() void CheckpointManager::UnlockAndClearTables(std::list& tables) { std::list::iterator it; - for (it = tables.begin(); it != tables.end(); ++it) { + for (it = tables.begin(); it != tables.end(); (void)++it) { Table* table = *it; if (table != nullptr) { table->Unlock(); @@ -491,7 +529,7 @@ void CheckpointManager::TaskDone(Table* table, uint32_t numSegs, bool success) MOT_ASSERT(table); if (success) { /* only successful tasks are added to the map file */ if (table == nullptr) { - OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Got a null table on task done"); + OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Got a null table on task done", nullptr); return; } MapFileEntry* entry = new (std::nothrow) MapFileEntry(); @@ -503,13 +541,14 @@ void CheckpointManager::TaskDone(Table* table, uint32_t numSegs, bool success) m_mapfileInfo.push_back(entry); m_finishedTasks.push_back(table); } else { - OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Failed to allocate map file entry"); + OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Failed to allocate map file entry", nullptr); return; } } if (--m_numCpTasks == 0) { m_checkpointEnded = true; + m_notifier.SetState(ThreadNotifier::ThreadState::SLEEP); } } @@ -517,17 +556,17 @@ void CheckpointManager::CompleteCheckpoint() { CheckpointControlFile* ctrlFile = CheckpointControlFile::GetCtrlFile(); if (ctrlFile == nullptr) { - OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Failed to retrieve control file object"); + OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "Failed to retrieve control file object", nullptr); return; } if (!CreateCheckpointMap()) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create map file"); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create map file", nullptr); return; } if (!ctrlFile->IsValid()) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Invalid control file"); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Invalid control file", nullptr); return; } @@ -535,12 +574,12 @@ void CheckpointManager::CompleteCheckpoint() (void)pthread_rwlock_wrlock(&m_fetchLock); do { if (!CreateEndFile()) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create completion file"); + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to create completion file", nullptr); break; } - if (!ctrlFile->Update(m_inProgressId, GetLsn(), GetLastReplayLsn())) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to update control file"); + if (!ctrlFile->Update(m_inProgressId, GetLsn(), GetLastReplayLsn(), GetTxnIdManager().GetCurrentId())) { + OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "Failed to update control file", nullptr); break; } @@ -555,46 +594,32 @@ void CheckpointManager::CompleteCheckpoint() } RemoveOldCheckpoints(m_inProgressId); - m_inProcessTxnsLsn = 0; - m_numSerializedEntries = 0; - MOT_LOG_INFO("Checkpoint [%lu] completed", m_inProgressId); + MOT_LOG_INFO("MOT checkpoint [%lu:%lu:%lu] completed", m_inProgressId, GetLsn(), GetLastReplayLsn()); } void CheckpointManager::DestroyCheckpointers() { if (m_checkpointers != nullptr) { + m_notifier.Notify(ThreadNotifier::ThreadState::TERMINATE); delete m_checkpointers; m_checkpointers = nullptr; } } -void CheckpointManager::CreateCheckpointers() -{ - m_checkpointers = new (std::nothrow) - CheckpointWorkerPool(m_numThreads, !m_availableBit, m_tasksList, m_cpSegThreshold, m_inProgressId, *this); -} - void CheckpointManager::Capture() { - MOT_LOG_DEBUG("CheckpointManager::capture"); - if (m_numCpTasks == 0) { MOT_LOG_INFO("No tasks in queue - empty checkpoint"); m_checkpointEnded = true; } else { - DestroyCheckpointers(); - CreateCheckpointers(); - if (m_checkpointers == nullptr) { - OnError(CheckpointWorkerPool::ErrCodes::MEMORY, "failed to spawn checkpoint threads"); - return; - } + m_notifier.Notify(ThreadNotifier::ThreadState::ACTIVE); } } void CheckpointManager::RemoveOldCheckpoints(uint64_t curCheckcpointId) { std::string workingDir = ""; - if (CheckpointUtils::GetWorkingDir(workingDir) == false) { + if (!CheckpointUtils::GetWorkingDir(workingDir)) { MOT_LOG_ERROR("RemoveOldCheckpoints: failed to get the working dir"); return; } @@ -602,11 +627,11 @@ void CheckpointManager::RemoveOldCheckpoints(uint64_t curCheckcpointId) DIR* dir = opendir(workingDir.c_str()); if (dir) { struct dirent* p; - while ((p = readdir(dir))) { + while ((p = readdir(dir)) != nullptr) { /* Skip the names "." and ".." and anything that is not chkpt_ */ if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..") || - strncmp(p->d_name, CheckpointUtils::dirPrefix, strlen(CheckpointUtils::dirPrefix)) || - strlen(p->d_name) <= strlen(CheckpointUtils::dirPrefix)) { + strncmp(p->d_name, CheckpointUtils::CKPT_DIR_PREFIX, strlen(CheckpointUtils::CKPT_DIR_PREFIX)) || + strlen(p->d_name) <= strlen(CheckpointUtils::CKPT_DIR_PREFIX)) { continue; } @@ -615,7 +640,7 @@ void CheckpointManager::RemoveOldCheckpoints(uint64_t curCheckcpointId) continue; } - uint64_t chkptId = strtoll(p->d_name + strlen(CheckpointUtils::dirPrefix), NULL, 10); + uint64_t chkptId = strtoll(p->d_name + strlen(CheckpointUtils::CKPT_DIR_PREFIX), NULL, 10); if (chkptId == curCheckcpointId) { MOT_LOG_DEBUG("RemoveOldCheckpoints: exclude %lu", chkptId); continue; @@ -623,7 +648,7 @@ void CheckpointManager::RemoveOldCheckpoints(uint64_t curCheckcpointId) MOT_LOG_DEBUG("RemoveOldCheckpoints: removing %lu", chkptId); RemoveCheckpointDir(chkptId); } - closedir(dir); + (void)closedir(dir); } else { MOT_LOG_ERROR("RemoveOldCheckpoints: failed to open dir: %s, error %d - %s", workingDir.c_str(), @@ -635,7 +660,7 @@ void CheckpointManager::RemoveOldCheckpoints(uint64_t curCheckcpointId) void CheckpointManager::RemoveCheckpointDir(uint64_t checkpointId) { errno_t erc; - char buf[CheckpointUtils::maxPath]; + char buf[CheckpointUtils::MAX_PATH]; std::string oldCheckpointDir; if (!CheckpointUtils::SetWorkingDir(oldCheckpointDir, checkpointId)) { MOT_LOG_ERROR("removeCheckpointDir: failed to set working directory"); @@ -645,29 +670,38 @@ void CheckpointManager::RemoveCheckpointDir(uint64_t checkpointId) DIR* dir = opendir(oldCheckpointDir.c_str()); if (dir != nullptr) { struct dirent* p; - while ((p = readdir(dir))) { + while ((p = readdir(dir)) != nullptr) { /* Skip the names "." and ".." */ - if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) + if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) { continue; + } struct stat statbuf = {0}; - erc = memset_s(buf, CheckpointUtils::maxPath, 0, CheckpointUtils::maxPath); + erc = memset_s(buf, CheckpointUtils::MAX_PATH, 0, CheckpointUtils::MAX_PATH); securec_check(erc, "\0", "\0"); erc = snprintf_s(buf, - CheckpointUtils::maxPath, - CheckpointUtils::maxPath - 1, + CheckpointUtils::MAX_PATH, + CheckpointUtils::MAX_PATH - 1, "%s/%s", oldCheckpointDir.c_str(), p->d_name); securec_check_ss(erc, "\0", "\0"); if (!stat(buf, &statbuf) && S_ISREG(statbuf.st_mode)) { MOT_LOG_DEBUG("removeCheckpointDir: deleting %s", buf); - unlink(buf); + if (unlink(buf) != 0) { + MOT_LOG_ERROR( + "removeCheckpointDir: failed to remove file %s, error %d:%s", buf, errno, gs_strerror(errno)); + } } } - closedir(dir); + (void)closedir(dir); MOT_LOG_DEBUG("removeCheckpointDir: removing dir %s", oldCheckpointDir.c_str()); - rmdir(oldCheckpointDir.c_str()); + if (rmdir(oldCheckpointDir.c_str()) != 0) { + MOT_LOG_ERROR("removeCheckpointDir: failed to remove directory %s, error %d:%s", + oldCheckpointDir.c_str(), + errno, + gs_strerror(errno)); + } } else { MOT_LOG_ERROR("removeCheckpointDir: failed to open dir: %s, error %d - %s", oldCheckpointDir.c_str(), @@ -680,15 +714,10 @@ bool CheckpointManager::CreateCheckpointMap() { int fd = -1; std::string fileName; - std::string workingDir; bool ret = false; do { - if (!CheckpointUtils::SetWorkingDir(workingDir, m_inProgressId)) { - break; - } - - CheckpointUtils::MakeMapFilename(fileName, workingDir, m_inProgressId); + CheckpointUtils::MakeMapFilename(fileName, m_workingDir, m_inProgressId); if (!CheckpointUtils::OpenFileWrite(fileName, fd)) { MOT_LOG_ERROR("createCheckpointMap: failed to create file '%s' - %d - %s", fileName.c_str(), @@ -697,27 +726,35 @@ bool CheckpointManager::CreateCheckpointMap() break; } - CheckpointUtils::MapFileHeader mapFileHeader{CP_MGR_MAGIC, m_mapfileInfo.size()}; - size_t wrStat = CheckpointUtils::WriteFile(fd, (char*)&mapFileHeader, sizeof(CheckpointUtils::MapFileHeader)); + CheckpointUtils::MapFileHeader mapFileHeader{CheckpointUtils::HEADER_MAGIC, m_mapfileInfo.size()}; + size_t wrStat = + CheckpointUtils::WriteFile(fd, (const char*)&mapFileHeader, sizeof(CheckpointUtils::MapFileHeader)); if (wrStat != sizeof(CheckpointUtils::MapFileHeader)) { MOT_LOG_ERROR( "createCheckpointMap: failed to write map file's header (%d) %d %s", wrStat, errno, gs_strerror(errno)); + (void)CheckpointUtils::CloseFile(fd); break; } - int i = 0; - for (std::list::iterator it = m_mapfileInfo.begin(); it != m_mapfileInfo.end(); ++it) { + size_t entryCount = 0; + for (std::list::iterator it = m_mapfileInfo.begin(); it != m_mapfileInfo.end(); (void)++it) { MapFileEntry* entry = *it; - if (CheckpointUtils::WriteFile(fd, (char*)entry, sizeof(MapFileEntry)) != sizeof(MapFileEntry)) { + if (CheckpointUtils::WriteFile(fd, (const char*)entry, sizeof(MapFileEntry)) != sizeof(MapFileEntry)) { MOT_LOG_ERROR("createCheckpointMap: failed to write map file entry"); - break; + break; // break out of for loop } delete entry; - i++; + entryCount++; + } + + if (entryCount != m_mapfileInfo.size()) { + (void)CheckpointUtils::CloseFile(fd); + break; } if (CheckpointUtils::FlushFile(fd)) { MOT_LOG_ERROR("createCheckpointMap: failed to flush map file"); + (void)CheckpointUtils::CloseFile(fd); break; } @@ -733,46 +770,27 @@ bool CheckpointManager::CreateCheckpointMap() void CheckpointManager::OnError(int errCode, const char* errMsg, const char* optionalMsg) { - m_stopFlag = true; - m_errorReportLock.lock(); + std::lock_guard lock(m_errorReportLock); if (!m_errorSet) { m_checkpointError = errCode; m_errorMessage.clear(); - m_errorMessage.append(errMsg); + (void)m_errorMessage.append(errMsg); if (optionalMsg != nullptr) { - m_errorMessage.append(" "); - m_errorMessage.append(optionalMsg); + (void)m_errorMessage.append(" "); + (void)m_errorMessage.append(optionalMsg); } m_errorSet = true; m_checkpointEnded = true; } - m_errorReportLock.unlock(); -} - -bool CheckpointManager::CreateCheckpointDir() -{ - std::string workingDir; - - if (!CheckpointUtils::SetWorkingDir(workingDir, m_inProgressId)) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "failed to setup working dir"); - return false; - } - - if (!CheckpointManager::CreateCheckpointDir(workingDir)) { - OnError(CheckpointWorkerPool::ErrCodes::FILE_IO, "failed to create working dir", workingDir.c_str()); - return false; - } - - return true; } bool CheckpointManager::CreateCheckpointId(uint64_t& checkpointId) { - if (CheckpointControlFile::GetCtrlFile() == nullptr) + if (CheckpointControlFile::GetCtrlFile() == nullptr) { return false; + } uint64_t curId = CheckpointControlFile::GetCtrlFile()->GetId(); - MOT::MOTEngine* engine = MOT::MOTEngine::GetInstance(); checkpointId = time(nullptr); while (true) { if (checkpointId == curId) { @@ -781,7 +799,7 @@ bool CheckpointManager::CreateCheckpointId(uint64_t& checkpointId) break; } } - MOT_LOG_DEBUG("createCheckpointId: %lu ,id:%lu", checkpointId, curId); + MOT_LOG_DEBUG("createCheckpointId: %lu, curId: %lu", checkpointId, curId); return true; } @@ -794,7 +812,7 @@ bool CheckpointManager::GetCheckpointDirName(std::string& dirName) return true; } -bool CheckpointManager::GetCheckpointWorkingDir(std::string& workingDir) +bool CheckpointManager::GetCheckpointWorkingDir(std::string& workingDir) const { if (!CheckpointUtils::GetWorkingDir(workingDir)) { MOT_LOG_ERROR("Could not obtain working directory"); @@ -803,7 +821,7 @@ bool CheckpointManager::GetCheckpointWorkingDir(std::string& workingDir) return true; } -bool CheckpointManager::CreateCheckpointDir(std::string& dir) +bool CheckpointManager::CreateCheckpointDir(const std::string& dir) { if (mkdir(dir.c_str(), S_IRWXU)) { /* 0700 */ MOT_LOG_DEBUG("Failed to create dir %s (%d:%s)", dir.c_str(), errno, gs_strerror(errno)); @@ -812,191 +830,96 @@ bool CheckpointManager::CreateCheckpointDir(std::string& dir) return true; } -bool CheckpointManager::CreateTpcRecoveryFile() +bool CheckpointManager::CreatePendingRecoveryDataFile() { int fd = -1; std::string fileName; - std::string workingDir; bool ret = false; - // This lock is held while serializing the in-process transactions by checkpoint. - MOTEngine::GetInstance()->GetInProcessTransactions().Lock(); - do { - if (MOTEngine::GetInstance()->GetInProcessTransactions().GetNumTxns() == 0) { - ret = true; - break; - } - - if (!CheckpointUtils::SetWorkingDir(workingDir, m_inProgressId)) { - break; - } - - CheckpointUtils::MakeTpcFilename(fileName, workingDir, m_inProgressId); + CheckpointUtils::MakeIpdFilename(fileName, m_workingDir, m_inProgressId); if (!CheckpointUtils::OpenFileWrite(fileName, fd)) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to create file '%s' [%d %s]", + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: Failed to create file '%s' - %d - %s", fileName.c_str(), errno, gs_strerror(errno)); break; } - m_inProcessTxnsLsn = MOTEngine::GetInstance()->GetInProcessTransactions().GetReplayLsn(); + CheckpointUtils::PendingTxnDataFileHeader ptdFileHeader; + ptdFileHeader.m_magic = CheckpointUtils::HEADER_MAGIC; + ptdFileHeader.m_numEntries = 0; // will be filled later on - CheckpointUtils::TpcFileHeader tpcFileHeader; - tpcFileHeader.m_magic = CP_MGR_MAGIC; - tpcFileHeader.m_numEntries = 0; - - if (!CheckpointUtils::SeekFile(fd, sizeof(CheckpointUtils::TpcFileHeader))) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to seek in file"); + if (lseek(fd, sizeof(CheckpointUtils::PendingTxnDataFileHeader), SEEK_SET) != + sizeof(CheckpointUtils::PendingTxnDataFileHeader)) { + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: lseek (1) failed [%d %s]", errno, gs_strerror(errno)); + (void)CheckpointUtils::CloseFile(fd); break; } - if (MOTEngine::GetInstance()->GetInProcessTransactions().GetNumTxns() > 0 && - SerializeInProcessTxns(fd) != RC_OK) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to serialize transactions [%d %s]", errno, gs_strerror(errno)); + uint64_t serializeStatus = GetRecoveryManager()->SerializePendingRecoveryData(fd); + if (serializeStatus == (uint64_t)(-1)) { + MOT_LOG_ERROR( + "CreatePendingRecoveryDataFile: Failed to serialize transactions [%d %s]", errno, gs_strerror(errno)); + (void)CheckpointUtils::CloseFile(fd); break; } - if (!CheckpointUtils::SeekFile(fd, 0)) { - MOTEngine::GetInstance()->GetInProcessTransactions().Unlock(); - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to seek in file"); + ptdFileHeader.m_numEntries = serializeStatus; + if (lseek(fd, 0, SEEK_SET) != 0) { + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: lseek (2) failed [%d %s]", errno, gs_strerror(errno)); + (void)CheckpointUtils::CloseFile(fd); break; } - tpcFileHeader.m_numEntries = m_numSerializedEntries; - - size_t wrStat = CheckpointUtils::WriteFile(fd, (char*)&tpcFileHeader, sizeof(CheckpointUtils::TpcFileHeader)); - if (wrStat != sizeof(CheckpointUtils::TpcFileHeader)) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to update tpc file's header (%d) [%d %s]", + size_t wrStat = CheckpointUtils::WriteFile( + fd, (const char*)&ptdFileHeader, sizeof(CheckpointUtils::PendingTxnDataFileHeader)); + if (wrStat != sizeof(CheckpointUtils::PendingTxnDataFileHeader)) { + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: Failed to write file's header (%d) [%d %s]", wrStat, errno, gs_strerror(errno)); + (void)CheckpointUtils::CloseFile(fd); break; } if (CheckpointUtils::FlushFile(fd)) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to flush map file"); + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: Failed to flush map file"); + (void)CheckpointUtils::CloseFile(fd); break; } if (CheckpointUtils::CloseFile(fd)) { - MOT_LOG_ERROR("CreateTpcRecoveryFile: failed to close map file"); + MOT_LOG_ERROR("CreatePendingRecoveryDataFile: Failed to close map file"); break; } - - MOT_LOG_INFO("Created tpc file with %lu entries", m_numSerializedEntries); ret = true; + MOT_LOG_INFO("CreatePendingRecoveryDataFile: Serialized %d entries", serializeStatus); } while (0); - - MOTEngine::GetInstance()->GetInProcessTransactions().Unlock(); return ret; } - -RC CheckpointManager::SerializeInProcessTxns(int fd) -{ - if (fd == -1) { - MOT_LOG_ERROR("SerializeInProcessTxns: bad fd"); - return RC_ERROR; - } - - m_numSerializedEntries = 0; - auto serializeLambda = [this, fd](RedoLogTransactionSegments* segments, uint64_t) -> RC { - errno_t erc; - LogSegment* segment = segments->GetSegment(segments->GetCount() - 1); - size_t bufSize = 0; - char* buf = nullptr; - CheckpointUtils::TpcEntryHeader header; - uint64_t csn = segment->m_controlBlock.m_csn; - for (uint32_t i = 0; i < segments->GetCount(); i++) { - segment = segments->GetSegment(i); - size_t sz = segment->SerializeSize(); - if (buf == nullptr) { - buf = (char*)malloc(sz); - MOT_LOG_DEBUG("SerializeInProcessTxns: alloc %lu - %p", sz, buf); - bufSize = sz; - } else if (sz > bufSize) { - char* bufTmp = (char*)malloc(sz); - if (bufTmp == nullptr) { - free(buf); - buf = nullptr; - } else { - erc = memcpy_s(bufTmp, sz, buf, bufSize); - securec_check(erc, "\0", "\0"); - free(buf); - buf = bufTmp; - } - MOT_LOG_DEBUG("SerializeInProcessTxns: realloc %lu - %p", sz, buf); - bufSize = sz; - } - - if (buf == nullptr) { - MOT_LOG_ERROR("SerializeInProcessTxns: failed to allocate buffer (%lu bytes)", sz); - return RC_ERROR; - } - - header.m_magic = CP_MGR_MAGIC; - header.m_len = bufSize; - segment->Serialize(buf); - size_t wrStat = CheckpointUtils::WriteFile(fd, (char*)&header, sizeof(CheckpointUtils::TpcEntryHeader)); - if (wrStat != sizeof(CheckpointUtils::TpcEntryHeader)) { - MOT_LOG_ERROR("SerializeInProcessTxns: failed to write header (wrote %lu) [%d:%s]", - wrStat, - errno, - gs_strerror(errno)); - free(buf); - return RC_ERROR; - } - - wrStat = CheckpointUtils::WriteFile(fd, buf, bufSize); - if (wrStat != bufSize) { - MOT_LOG_ERROR("SerializeInProcessTxns: failed to write %lu bytes to file (wrote %lu) [%d:%s]", - bufSize, - wrStat, - errno, - gs_strerror(errno)); - free(buf); - return RC_ERROR; - } - - m_numSerializedEntries++; - MOT_LOG_DEBUG("SerializeInProcessTxns: wrote seg %p %lu bytes", segment, bufSize); - } - if (buf != nullptr) { - free(buf); - } - return RC_OK; - }; - - return MOTEngine::GetInstance()->GetInProcessTransactions().ForEachTransactionNoLock(serializeLambda); -} - bool CheckpointManager::CreateEndFile() { int fd = -1; std::string fileName; - std::string workingDir; bool ret = false; do { - if (!CheckpointUtils::SetWorkingDir(workingDir, m_inProgressId)) { - break; - } - - CheckpointUtils::MakeEndFilename(fileName, workingDir, m_inProgressId); + CheckpointUtils::MakeEndFilename(fileName, m_workingDir, m_inProgressId); if (!CheckpointUtils::OpenFileWrite(fileName, fd)) { MOT_LOG_ERROR( - "CreateEndFile: failed to create file '%s' - %d - %s", fileName.c_str(), errno, gs_strerror(errno)); + "CreateEndFile: Failed to create file '%s' - %d - %s", fileName.c_str(), errno, gs_strerror(errno)); break; } if (CheckpointUtils::FlushFile(fd)) { - MOT_LOG_ERROR("CreateEndFile: failed to flush map file"); + MOT_LOG_ERROR("CreateEndFile: Failed to flush end file"); + (void)CheckpointUtils::CloseFile(fd); break; } if (CheckpointUtils::CloseFile(fd)) { - MOT_LOG_ERROR("CreateEndFile: failed to close map file"); + MOT_LOG_ERROR("CreateEndFile: Failed to close end file"); break; } ret = true; diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.h b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.h index 548afe99c..24ec62724 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.h +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_manager.h @@ -47,7 +47,7 @@ class CheckpointManager : public CheckpointManagerCallbacks { public: CheckpointManager(); - virtual ~CheckpointManager(); + ~CheckpointManager() override; bool Initialize(); @@ -107,7 +107,12 @@ public: * @brief Sets stable row according to the checkpoint state. * @param txn Transaction's TxnManger pointer. */ - void ApplyWrite(TxnManager* txnMan, Row* origRow, AccessType type); + void ApplyWrite(TxnManager* txnMan, Row* origRow, const Access* access); + + uint32_t GetNumWorkers() const override + { + return m_numThreads; + } /** * @brief Checkpoint task completion callback @@ -116,11 +121,21 @@ public: * @param numSegs number of segments written. * @param success Indicates a success or a failure. */ - virtual void TaskDone(Table* table, uint32_t numSegs, bool success); + void TaskDone(Table* table, uint32_t numSegs, bool success) override; - virtual bool ShouldStop() const + std::string& GetWorkingDir() override { - return m_stopFlag; + return m_workingDir; + } + + bool GetNotAvailableBit() const override + { + return !m_availableBit; + } + + std::list& GetTasksList() override + { + return m_tasksList; } /** @@ -129,7 +144,12 @@ public: * @param errMsg The error's message. * @param optionalMsg An optional message to display. */ - virtual void OnError(int errCode, const char* errMsg, const char* optionalMsg = nullptr); + void OnError(int errCode, const char* errMsg, const char* optionalMsg) override; + + ThreadNotifier& GetThreadNotifier() override + { + return m_notifier; + } /** * @brief Deletes 'old' checkpoint directories @@ -159,9 +179,9 @@ public: * @param dir The directory path to create * @return Boolean value denoting success or failure. */ - static bool CreateCheckpointDir(std::string& dir); + static bool CreateCheckpointDir(const std::string& dir); - uint64_t GetId() + uint64_t GetId() const { return m_id; } @@ -171,7 +191,7 @@ public: m_id = id; } - uint64_t GetLastReplayLsn() + uint64_t GetLastReplayLsn() const { return m_lastReplayLsn; } @@ -188,7 +208,7 @@ public: bool GetCheckpointDirName(std::string& dirName); - bool GetCheckpointWorkingDir(std::string& workingDir); + bool GetCheckpointWorkingDir(std::string& workingDir) const; CheckpointManager(const CheckpointManager& orig) = delete; @@ -225,7 +245,9 @@ private: CheckpointWorkerPool* m_checkpointers = nullptr; // Number of threads to run - int m_numThreads; + uint32_t m_numThreads; + + ThreadNotifier m_notifier; // mutex for safeguarding mapfile and tasks queues access std::mutex m_tasksMutex; @@ -235,9 +257,6 @@ private: // Checkpoint segments size threshold uint32_t m_cpSegThreshold; - // Signal working threads to exit - volatile bool m_stopFlag; - // Indicates checkpoint has ended volatile bool m_checkpointEnded; @@ -268,6 +287,9 @@ private: // last seen recovery lsn uint64_t m_lastReplayLsn; + // Current working dir + std::string m_workingDir; + // The most recent segment's lsn that was inserted to the in-process map uint64_t m_inProcessTxnsLsn; @@ -292,7 +314,7 @@ private: m_lsn = lsn; } - uint64_t GetLsn() + uint64_t GetLsn() const { return m_lsn; } @@ -309,12 +331,6 @@ private: m_availableBit = !m_availableBit; } - /** - * @brief Creates the checkpoint directory. - * @return Boolean value denoting success or failure. - */ - bool CreateCheckpointDir(); - /** * @brief Performs checkpoint completion tasks: * updates control file, creates the map file @@ -323,7 +339,7 @@ private: void CompleteCheckpoint(); /** - * @brief Performs the checkpoint's Capture phase + * @brief Performs the checkpoint's Capture phase. */ void Capture(); @@ -344,11 +360,6 @@ private: */ void DestroyCheckpointers(); - /** - * @brief Creates the checkpoint threads - */ - void CreateCheckpointers(); - /** * @brief Ensures that before moving to a new state, all transactions that * started committing on previous phase will complete. This is needed @@ -363,10 +374,10 @@ private: inline bool IsAutoCompletePhase() const { - if (m_phase == PREPARE) + if (m_phase == PREPARE) { return true; - else - return false; + } + return false; } /** @@ -377,11 +388,10 @@ private: bool CreateCheckpointMap(); /** - * @brief Saves the in-process transaction data for 2pc recovery - * purposes during the checkpoint. + * @brief Saves pending transaction data. * @return Boolean value denoting success or failure. */ - bool CreateTpcRecoveryFile(); + bool CreatePendingRecoveryDataFile(); /** * @brief Creates a file that indicates checkpoint completion. @@ -389,12 +399,6 @@ private: */ bool CreateEndFile(); - /** - * @brief Serializes inProcess transactions to disk - * @return RC value denoting the status of the operation. - */ - RC SerializeInProcessTxns(int fd); - void ResetFlags(); /** diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp index 1e4a806e8..abe138efe 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp @@ -32,7 +32,7 @@ DECLARE_LOGGER(CheckpointUtils, Checkpoint); namespace CheckpointUtils { -bool IsFileExists(std::string fileName) +bool IsFileExists(const std::string& fileName) { struct stat statBuf = {0}; if (stat(fileName.c_str(), &statBuf) == -1 && errno == ENOENT) { @@ -43,7 +43,7 @@ bool IsFileExists(std::string fileName) return true; } -bool IsDirExists(std::string dirName) +bool IsDirExists(const std::string& dirName) { struct stat statBuf = {0}; if (stat(dirName.c_str(), &statBuf) == -1 && errno == ENOENT) { @@ -54,7 +54,7 @@ bool IsDirExists(std::string dirName) return true; } -bool OpenFileWrite(std::string fileName, FILE*& pFile) +bool OpenFileWrite(const std::string& fileName, FILE*& pFile) { pFile = fopen(fileName.c_str(), "wb"); if (pFile == nullptr) { @@ -64,7 +64,7 @@ bool OpenFileWrite(std::string fileName, FILE*& pFile) return true; } -bool OpenFileRead(std::string fileName, FILE*& pFile) +bool OpenFileRead(const std::string& fileName, FILE*& pFile) { pFile = fopen(fileName.c_str(), "rb"); if (pFile == nullptr) { @@ -74,7 +74,7 @@ bool OpenFileRead(std::string fileName, FILE*& pFile) return true; } -bool OpenFileWrite(std::string fileName, int& fd) +bool OpenFileWrite(const std::string& fileName, int& fd) { fd = open(fileName.c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); /* 0600 */ if (fd == -1) { @@ -83,7 +83,7 @@ bool OpenFileWrite(std::string fileName, int& fd) return (fd != -1); } -bool OpenFileRead(std::string fileName, int& fd) +bool OpenFileRead(const std::string& fileName, int& fd) { fd = open(fileName.c_str(), O_RDONLY); if (fd == -1) { @@ -101,7 +101,7 @@ int CloseFile(int fd) return rc; } -size_t WriteFile(int fd, char* data, size_t len) +size_t WriteFile(int fd, const char* data, size_t len) { ssize_t wrote = write(fd, (const void*)data, len); if (wrote == -1) { @@ -144,17 +144,17 @@ bool SeekFile(int fd, uint64_t offset) bool GetWorkingDir(std::string& dir) { dir.clear(); - char cwd[maxPath] = {0}; + char cwd[MAX_PATH] = {0}; size_t checkpointDirLength = GetGlobalConfiguration().m_checkpointDir.length(); if (checkpointDirLength > 0) { - errno_t erc = strncpy_s(cwd, maxPath, GetGlobalConfiguration().m_checkpointDir.c_str(), checkpointDirLength); + errno_t erc = strncpy_s(cwd, MAX_PATH, GetGlobalConfiguration().m_checkpointDir.c_str(), checkpointDirLength); securec_check(erc, "\0", "\0"); } else if (!getcwd(cwd, sizeof(cwd))) { MOT_REPORT_SYSTEM_ERROR(getcwd, "N/A", "Failed to get current working directory"); return false; } - dir.append(cwd); - dir.append("/"); + (void)dir.append(cwd); + (void)dir.append("/"); return true; } @@ -177,7 +177,7 @@ void Hexdump(const char* msg, char* b, uint32_t buflen) } if (msg != nullptr) { - fprintf(stderr, "%s (len %u)\n", msg, buflen); + (void)fprintf(stderr, "%s (len %u)\n", msg, buflen); } for (i = 0; i < buflen; i += elemSize) { @@ -224,7 +224,7 @@ void Hexdump(const char* msg, char* b, uint32_t buflen) securec_check(erc, "\0", "\0"); } } - fprintf(stderr, "%s\n", line); + (void)fprintf(stderr, "%s\n", line); } } } // namespace CheckpointUtils diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h index 42b6b919e..0122afa27 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h @@ -33,24 +33,49 @@ #include #include -const uint64_t CP_MGR_MAGIC = 0xaabbccdd; - namespace MOT { namespace CheckpointUtils { +// Checkpoint file header magic number +const uint64_t HEADER_MAGIC = 0xaabbccdd; + +// Checkpoint dir prefix +const char* const CKPT_DIR_PREFIX = "chkpt_"; + +// Checkpoint file suffix +const char* const CKPT_FILE_SUFFIX = ".cp"; + +// Metadata file suffix +const char* const MD_FILE_SUFFIX = ".md"; + +// Map file suffix +const char* const MAP_FILE_SUFFIX = ".map"; + +// TPC file suffix +const char* const TPC_FILE_SUFFIX = ".tpc"; + +// PTD file suffix +const char* const IPD_FILE_SUFFIX = ".ptd"; + +// End file suffix +const char* const END_FILE_SUFFIX = ".end"; + +// Max path length +const size_t MAX_PATH = 1024; + /** * @brief A wrapper function that checks if a file exists * @param fileName The file name to check * @return Boolean value denoting success or failure. */ -bool IsFileExists(std::string fileName); +bool IsFileExists(const std::string& fileName); /** * @brief A wrapper function that checks if a dir exists * @param fileName The directory name to check * @return Boolean value denoting success or failure. */ -bool IsDirExists(std::string dirName); +bool IsDirExists(const std::string& dirName); /** * @brief A wrapper function that opens a file for writing. @@ -58,7 +83,7 @@ bool IsDirExists(std::string dirName); * @param pFile The returned FILE* pointer * @return Boolean value denoting success or failure. */ -bool OpenFileWrite(std::string fileName, FILE*& pFile); +bool OpenFileWrite(const std::string& fileName, FILE*& pFile); /** * @brief A wrapper function that opens a file for reading. @@ -66,7 +91,7 @@ bool OpenFileWrite(std::string fileName, FILE*& pFile); * @param pFile The returned FILE* pointer * @return Boolean value denoting success or failure. */ -bool OpenFileRead(std::string fileName, FILE*& pFile); +bool OpenFileRead(const std::string& fileName, FILE*& pFile); /** * @brief A wrapper function that opens a file for writing. @@ -74,7 +99,7 @@ bool OpenFileRead(std::string fileName, FILE*& pFile); * @param fd The returned file descriptor * @return Boolean value denoting success or failure. */ -bool OpenFileWrite(std::string fileName, int& fd); +bool OpenFileWrite(const std::string& fileName, int& fd); /** * @brief A wrapper function that opens a file for reading. @@ -82,7 +107,7 @@ bool OpenFileWrite(std::string fileName, int& fd); * @param fd The returned file descriptor * @return Boolean value denoting success or failure. */ -bool OpenFileRead(std::string fileName, int& fd); +bool OpenFileRead(const std::string& fileName, int& fd); /** * @brief a wrapper function that closes a file fd. @@ -98,7 +123,7 @@ int CloseFile(int fd); * @param len The number of bytes to write * @return size_t value equals to the bytes that were written. */ -size_t WriteFile(int fd, char* data, size_t len); +size_t WriteFile(int fd, const char* data, size_t len); /** * @brief A wrapper function that reads from a file fd. @@ -143,7 +168,7 @@ inline void DestroyStableRow(Row* row) inline bool CreateStableRow(Row* origRow) { Row* tmpRow = nullptr; - Sentinel* s = origRow->GetPrimarySentinel(); + PrimarySentinel* s = origRow->GetPrimarySentinel(); MOT_ASSERT(s); if (s == nullptr) { return false; @@ -159,30 +184,10 @@ inline bool CreateStableRow(Row* origRow) tmpRow = s->GetStable(); tmpRow->DeepCopy(origRow); } + s->GetStable()->SetStableTid(s->GetTransactionId()); return true; } -// Checkpoint dir prefix -static const char* dirPrefix = "chkpt_"; - -// Checkpoint file suffix -static const char* cpFileSuffix = ".cp"; - -// Metadata file suffix -static const char* mdFileSuffix = ".md"; - -// Map file suffix -static const char* mapFileSuffix = ".map"; - -// TPC file suffix -static const char* tpcFileSuffix = ".tpc"; - -// End file suffix -static const char* validFileSuffix = ".end"; - -// Max path len -static const size_t maxPath = 1024; - /** * @brief Returns the current working directory. * @param dir The returned directory string. @@ -199,8 +204,8 @@ bool GetWorkingDir(std::string& dir); */ inline bool SetDirName(std::string& workingDir, uint64_t cpId) { - workingDir.append(dirPrefix); - workingDir.append(std::to_string(cpId)); + (void)workingDir.append(CKPT_DIR_PREFIX); + (void)workingDir.append(std::to_string(cpId)); return true; } @@ -224,11 +229,11 @@ inline bool SetWorkingDir(std::string& workingDir, uint64_t cpId) * @param fileName The returned prefix for the filename. * @param workingDir The directory in which the file is located. */ -inline void MakeFilename(std::string& fileName, std::string& workingDir) +inline void MakeFilename(std::string& fileName, const std::string& workingDir) { fileName.clear(); - fileName.append(workingDir); - fileName.append("/"); + (void)fileName.append(workingDir); + (void)fileName.append("/"); } /** @@ -237,11 +242,11 @@ inline void MakeFilename(std::string& fileName, std::string& workingDir) * @param workingDir The directory in which the file should be located. * @param cpId The checkpoint id. */ -inline void MakeMapFilename(std::string& fileName, std::string& workingDir, uint64_t cpId) +inline void MakeMapFilename(std::string& fileName, const std::string& workingDir, uint64_t cpId) { MakeFilename(fileName, workingDir); - fileName.append(std::to_string(cpId)); - fileName.append(mapFileSuffix); + (void)fileName.append(std::to_string(cpId)); + (void)fileName.append(MAP_FILE_SUFFIX); } /** @@ -251,14 +256,14 @@ inline void MakeMapFilename(std::string& fileName, std::string& workingDir, uint * @param workingDir The directory in which the file should be located. * @param seg The segment number. */ -inline void MakeCpFilename(uint64_t tableId, std::string& fileName, std::string& workingDir, int seg = 0) +inline void MakeCpFilename(uint64_t tableId, std::string& fileName, const std::string& workingDir, int seg = 0) { MakeFilename(fileName, workingDir); - fileName.append("tab_"); - fileName.append(std::to_string(tableId)); - fileName.append("_"); - fileName.append(std::to_string(seg)); - fileName.append(cpFileSuffix); + (void)fileName.append("tab_"); + (void)fileName.append(std::to_string(tableId)); + (void)fileName.append("_"); + (void)fileName.append(std::to_string(seg)); + (void)fileName.append(CKPT_FILE_SUFFIX); } /** @@ -267,11 +272,11 @@ inline void MakeCpFilename(uint64_t tableId, std::string& fileName, std::string& * @param fileName The returned filename string. * @param workingDir The directory in which the file should be located. */ -inline void MakeMdFilename(uint64_t tableId, std::string& fileName, std::string& workingDir) +inline void MakeMdFilename(uint64_t tableId, std::string& fileName, const std::string& workingDir) { MakeFilename(fileName, workingDir); - fileName.append(std::to_string(tableId)); - fileName.append(mdFileSuffix); + (void)fileName.append(std::to_string(tableId)); + (void)fileName.append(MD_FILE_SUFFIX); } /** @@ -280,11 +285,24 @@ inline void MakeMdFilename(uint64_t tableId, std::string& fileName, std::string& * @param workingDir The directory in which the file should be located. * @param cpId The checkpoint id. */ -inline void MakeTpcFilename(std::string& fileName, std::string& workingDir, uint64_t cpId) +inline void MakeTpcFilename(std::string& fileName, const std::string& workingDir, uint64_t cpId) { MakeFilename(fileName, workingDir); - fileName.append(std::to_string(cpId)); - fileName.append(tpcFileSuffix); + (void)fileName.append(std::to_string(cpId)); + (void)fileName.append(TPC_FILE_SUFFIX); +} + +/** + * @brief Creates an in-process data filename. + * @param fileName The returned filename string. + * @param workingDir The directory in which the file should be located. + * @param cpId The checkpoint id. + */ +inline void MakeIpdFilename(std::string& fileName, const std::string& workingDir, uint64_t cpId) +{ + MakeFilename(fileName, workingDir); + (void)fileName.append(std::to_string(cpId)); + (void)fileName.append(IPD_FILE_SUFFIX); } /** @@ -293,11 +311,11 @@ inline void MakeTpcFilename(std::string& fileName, std::string& workingDir, uint * @param workingDir The directory in which the file should be located. * @param cpId The checkpoint id. */ -inline void MakeEndFilename(std::string& fileName, std::string& workingDir, uint64_t cpId) +inline void MakeEndFilename(std::string& fileName, const std::string& workingDir, uint64_t cpId) { MakeFilename(fileName, workingDir); - fileName.append(std::to_string(cpId)); - fileName.append(validFileSuffix); + (void)fileName.append(std::to_string(cpId)); + (void)fileName.append(END_FILE_SUFFIX); } /** @@ -321,13 +339,23 @@ struct FileHeader { uint64_t m_numOps; }; -struct EntryHeader { +struct EntryHeaderBase { uint64_t m_csn; uint64_t m_rowId; uint32_t m_dataLen; uint16_t m_keyLen; }; +struct EntryHeader { + EntryHeaderBase m_base; + uint64_t m_transactionId; +}; + +struct MetaFileHeaderBase { + FileHeader m_fileHeader; + EntryHeaderBase m_entryHeader; +}; + struct MetaFileHeader { FileHeader m_fileHeader; EntryHeader m_entryHeader; @@ -338,11 +366,12 @@ struct MapFileHeader { uint64_t m_numEntries; }; -struct TpcFileHeader { +struct PendingTxnDataFileHeader { uint64_t m_magic; uint64_t m_numEntries; }; +/* Deprecated, used in older versions (metaVersion < METADATA_VER_LOW_RTO). */ struct TpcEntryHeader { uint64_t m_magic; uint64_t m_len; diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.cpp b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.cpp index 709648004..cc9388915 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.cpp +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.cpp @@ -22,60 +22,70 @@ * ------------------------------------------------------------------------- */ -#include #include #include #include -#include +#include #include "checkpoint_utils.h" #include "checkpoint_worker.h" -#include "checkpoint_manager.h" #include "mot_engine.h" namespace MOT { DECLARE_LOGGER(CheckpointWorkerPool, Checkpoint); -struct WorkerThreads { - std::vector m_vec; -}; +static const char* const CKPT_WORKER_NAME = "CheckpointWorker"; -void CheckpointWorkerPool::Start() +static bool Wakeup(void* obj) { - MOT_LOG_DEBUG("CheckpointWorkerPool::start() %d workers", m_numWorkers.load()); - - if (!CheckpointUtils::SetWorkingDir(m_workingDir, m_checkpointId)) { - m_cpManager.OnError(ErrCodes::FILE_IO, "failed to setup working dir"); + CheckpointManagerCallbacks* cpManager = (CheckpointManagerCallbacks*)obj; + if (cpManager != nullptr) { + return ((cpManager->GetThreadNotifier().GetState() == ThreadNotifier::ThreadState::TERMINATE) || + (cpManager->GetThreadNotifier().GetState() == ThreadNotifier::ThreadState::ACTIVE && + !cpManager->GetTasksList().empty())); } + return true; +} - WorkerThreads* threads = new (std::nothrow) WorkerThreads(); - if (threads == nullptr) { - m_cpManager.OnError(ErrCodes::MEMORY, "failed to allocate checkpoint thread pool"); - return; +bool CheckpointWorkerPool::Start() +{ + uint32_t numWorkers = m_cpManager.GetNumWorkers(); + MOT_LOG_TRACE("CheckpointWorkerPool::Start() %u workers", numWorkers); + for (uint32_t i = 0; i < numWorkers; ++i) { + ThreadContext* workerContext = new (std::nothrow) ThreadContext(); + if (workerContext == nullptr) { + MOT_LOG_ERROR("CheckpointWorkerPool::Start: Failed to allocate context for %s%u", CKPT_WORKER_NAME, i); + return false; + } + + m_workerContexts.push_back(workerContext); + m_workers.push_back(std::thread(&CheckpointWorkerPool::WorkerFunc, this, i)); + + if (!WaitForThreadStart(m_workerContexts.at(i))) { + MOT_LOG_ERROR("CheckpointWorkerPool::Start: Failed to start %s%u", CKPT_WORKER_NAME, i); + return false; + } } - - for (uint32_t i = 0; i < m_numWorkers; ++i) { - threads->m_vec.push_back(std::thread(&CheckpointWorkerPool::WorkerFunc, this)); - } - - m_workers = (void*)threads; + return true; } CheckpointWorkerPool::~CheckpointWorkerPool() { - - MOT_LOG_DEBUG("~CheckpointWorkerPool: waiting for workers"); - WorkerThreads* threads = reinterpret_cast(m_workers); - for (auto& worker : threads->m_vec) { - if (worker.joinable()) + MOT_LOG_TRACE("~CheckpointWorkerPool: Waiting for workers"); + for (auto& worker : m_workers) { + if (worker.joinable()) { worker.join(); + } } - - delete threads; - - MOT_LOG_DEBUG("~CheckpointWorkerPool: done"); + m_workers.clear(); + for (auto& workerContext : m_workerContexts) { + delete workerContext; + workerContext = nullptr; + } + m_workerContexts.clear(); + MOT_LOG_DEBUG("~CheckpointWorkerPool: Done"); } -bool CheckpointWorkerPool::Write(Buffer* buffer, Row* row, int fd) +bool CheckpointWorkerPool::Write(Buffer* buffer, Row* row, int fd, uint64_t transactionId) { MaxKey key; Key* primaryKey = &key; @@ -85,9 +95,9 @@ bool CheckpointWorkerPool::Write(Buffer* buffer, Row* row, int fd) if (buffer->Size() + primaryKey->GetKeyLength() + row->GetTupleSize() + sizeof(CheckpointUtils::EntryHeader) > buffer->MaxSize()) { // need to flush the buffer before serializing the next row - size_t wrSta = CheckpointUtils::WriteFile(fd, (char*)buffer->Data(), buffer->Size()); + size_t wrSta = CheckpointUtils::WriteFile(fd, (const char*)buffer->Data(), buffer->Size()); if (wrSta != buffer->Size()) { - MOT_LOG_ERROR("CheckpointWorkerPool::write - failed to write %u bytes to [%d] (%d:%s)", + MOT_LOG_ERROR("CheckpointWorkerPool::Write - Failed to write %u bytes to [%d] (%d:%s)", buffer->Size(), fd, errno, @@ -95,78 +105,80 @@ bool CheckpointWorkerPool::Write(Buffer* buffer, Row* row, int fd) return false; } - if (CheckpointUtils::FlushFile(fd)) { - MOT_LOG_ERROR("CheckpointWorkerPool::write - failed to flush [%d]", fd); - return false; - } buffer->Reset(); } CheckpointUtils::EntryHeader entryHeader; - entryHeader.m_keyLen = primaryKey->GetKeyLength(); - entryHeader.m_dataLen = row->GetTupleSize(); - entryHeader.m_csn = row->GetCommitSequenceNumber(); - entryHeader.m_rowId = row->GetRowId(); - MOT_ASSERT((entryHeader.m_csn != CSNManager::INVALID_CSN) && (entryHeader.m_rowId != Row::INVALID_ROW_ID)); + entryHeader.m_base.m_keyLen = primaryKey->GetKeyLength(); + entryHeader.m_base.m_dataLen = row->GetTupleSize(); + entryHeader.m_base.m_csn = row->GetCommitSequenceNumber(); + entryHeader.m_base.m_rowId = row->GetRowId(); + entryHeader.m_transactionId = transactionId; + MOT_ASSERT((entryHeader.m_base.m_csn != INVALID_CSN) && (entryHeader.m_base.m_rowId != Row::INVALID_ROW_ID)); if (!buffer->Append(&entryHeader, sizeof(CheckpointUtils::EntryHeader))) { - MOT_LOG_ERROR("CheckpointWorkerPool::Write Failed to write entry to buffer"); + MOT_LOG_ERROR("CheckpointWorkerPool::Write - Failed to write entry to buffer"); return false; } if (!buffer->Append(primaryKey->GetKeyBuf(), primaryKey->GetKeyLength())) { - MOT_LOG_ERROR("CheckpointWorkerPool::Write Failed to write entry to buffer"); + MOT_LOG_ERROR("CheckpointWorkerPool::Write - Failed to write entry to buffer"); return false; } if (!buffer->Append(row->GetData(), row->GetTupleSize())) { - MOT_LOG_ERROR("CheckpointWorkerPool::Write Failed to write entry to buffer"); + MOT_LOG_ERROR("CheckpointWorkerPool::Write - Failed to write entry to buffer"); return false; } return true; } -int CheckpointWorkerPool::Checkpoint(Buffer* buffer, Sentinel* sentinel, int fd, uint16_t threadId, bool& isDeleted) +int CheckpointWorkerPool::Checkpoint( + Buffer* buffer, PrimarySentinel* sentinel, int fd, uint16_t threadId, bool& isDeleted, Row*& deletedVersion) { - Row* mainRow = sentinel->GetData(); + Row* mainRow = nullptr; Row* stableRow = nullptr; int wrote = 0; isDeleted = false; + deletedVersion = nullptr; - if (mainRow == nullptr) { + sentinel->Lock(threadId); + + if (sentinel->IsCommited() == false) { + sentinel->Unlock(); return 0; } - bool headerLocked = sentinel->TryLock(threadId); - if (headerLocked == false) { - if (mainRow->GetTwoPhaseMode() == true) { - MOT_LOG_DEBUG("checkpoint: row %p is 2pc", mainRow); - return wrote; - } - sentinel->Lock(threadId); + stableRow = sentinel->GetStable(); + mainRow = sentinel->GetData(); + if (mainRow == nullptr) { + sentinel->Unlock(); + return 0; } - stableRow = sentinel->GetStable(); if (mainRow->IsRowDeleted()) { if (stableRow) { // Truly deleted and was not removed by txn manager isDeleted = true; + (void)sentinel->GetGcInfo().RefCountUpdate(INC); + deletedVersion = mainRow; } else { MOT_LOG_DEBUG("Detected Deleted row without Stable Row!"); } } bool statusBit = sentinel->GetStableStatus(); - bool deleted = !sentinel->IsCommited(); /* this currently indicates if the row is deleted or not */ + /* this currently indicates if the row is deleted or not */ + bool deleted = !sentinel->IsCommited() or mainRow->IsRowDeleted(); MOT_ASSERT(sentinel->GetStablePreAllocStatus() == false); do { - if (statusBit == !m_na) { /* has stable version */ + if (statusBit == !m_cpManager.GetNotAvailableBit()) { /* has stable version */ if (stableRow == nullptr) { break; } - if (!Write(buffer, stableRow, fd)) { + if (!Write(buffer, stableRow, fd, stableRow->GetStableTid())) { wrote = -1; } else { - if (isDeleted == false) { + if (!isDeleted) { CheckpointUtils::DestroyStableRow(stableRow); sentinel->SetStable(nullptr); } @@ -179,8 +191,8 @@ int CheckpointWorkerPool::Checkpoint(Buffer* buffer, Sentinel* sentinel, int fd, wrote = 0; break; } - sentinel->SetStableStatus(!m_na); - if (!Write(buffer, mainRow, fd)) { + sentinel->SetStableStatus(!m_cpManager.GetNotAvailableBit()); + if (!Write(buffer, mainRow, fd, mainRow->GetPrimarySentinel()->GetTransactionId())) { wrote = -1; // we failed to write, set error } else { wrote = 1; @@ -199,124 +211,177 @@ int CheckpointWorkerPool::Checkpoint(Buffer* buffer, Sentinel* sentinel, int fd, sentinel->SetStable(nullptr); } - sentinel->Release(); + sentinel->Unlock(); return wrote; } Table* CheckpointWorkerPool::GetTask() { + std::lock_guard lock(m_tasksLock); Table* table = nullptr; - m_tasksLock.lock(); do { - if (m_tasksList.empty()) + if (m_cpManager.GetTasksList().empty()) { break; - table = m_tasksList.front(); - m_tasksList.pop_front(); + } + table = m_cpManager.GetTasksList().front(); + m_cpManager.GetTasksList().pop_front(); } while (0); - m_tasksLock.unlock(); return table; } -void CheckpointWorkerPool::ExecuteMicroGcTransaction( - Sentinel** deletedList, GcManager* gcSession, Table* table, uint16_t& deletedCounter, uint16_t limit) +bool CheckpointWorkerPool::ExecuteMicroGcTransaction( + DeletePair* deletedList, GcManager* gcSession, Table* table, uint16_t& deletedCounter, uint16_t limit) { if (deletedCounter == limit) { - gcSession->GcStartTxn(); + // Reserve GC Memory for the current batch + if (gcSession->ReserveGCMemory(limit) == false) { + deletedCounter = 0; + MOT_LOG_ERROR("CheckpointWorkerPool::ExecuteMicroGcTransaction: ReserveGCMemory failed"); + return false; + } for (uint16_t i = 0; i < limit; i++) { - Row* out = table->RemoveKeyFromIndex(deletedList[i]->GetData(), deletedList[i], 0, gcSession); + DeletePair& element = deletedList[i]; + PrimarySentinel* ps = element.first; + Row* r = element.second; + gcSession->GcRecordObject(GC_QUEUE_TYPE::DELETE_QUEUE, + ps->GetIndex()->GetIndexId(), + r, + ps, + Row::DeleteRowDtor, + ROW_SIZE_FROM_POOL(ps->GetIndex()->GetTable()), + r->GetCommitSequenceNumber()); } gcSession->GcCheckPointClean(); deletedCounter = 0; + if (gcSession->GcStartTxn() != RC_OK) { + MOT_LOG_ERROR("CheckpointWorkerPool::ExecuteMicroGcTransaction: GcStartTxn failed"); + return false; + } } + return true; } -void CheckpointWorkerPool::WorkerFunc() +void CheckpointWorkerPool::WorkerFunc(uint32_t workerId) { MOT_DECLARE_NON_KERNEL_THREAD(); - MOT_LOG_DEBUG("CheckpointWorkerPool::WorkerFunc"); + + char threadName[ThreadContext::THREAD_NAME_LEN]; + errno_t rc = snprintf_s(threadName, + ThreadContext::THREAD_NAME_LEN, + ThreadContext::THREAD_NAME_LEN - 1, + "%s%u", + CKPT_WORKER_NAME, + workerId); + securec_check_ss(rc, "", ""); + + (void)pthread_setname_np(pthread_self(), threadName); + + MOT_LOG_INFO("%s - Starting", threadName); + + ThreadContext* workerContext = m_workerContexts[workerId]; + MOT_ASSERT(workerContext != nullptr); Buffer buffer; - if (!buffer.Initialize()) { - MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to initialize buffer"); - m_cpManager.OnError(ErrCodes::MEMORY, "Memory allocation failure"); - MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); - MOT_LOG_DEBUG("thread exiting"); - return; - } - SessionContext* sessionContext = GetSessionManager()->CreateSessionContext(); if (sessionContext == nullptr) { MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to initialize Session Context"); m_cpManager.OnError(ErrCodes::MEMORY, "Memory allocation failure"); + workerContext->SetError(); MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); - MOT_LOG_DEBUG("thread exiting"); + MOT_LOG_DEBUG("%s - Exiting", threadName); return; } GcManager* gcSession = sessionContext->GetTxnManager()->GetGcSession(); - gcSession->SetGcType(GcManager::GC_CHECKPOINT); + gcSession->SetGcType(GcManager::GC_TYPE::GC_CHECKPOINT); + bool res = gcSession->ReserveGCMemory(MAX_ITERS_COUNT * 2); + if (!res) { + MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to Reserve GC Memory"); + m_cpManager.OnError(ErrCodes::MEMORY, "Memory allocation failure"); + workerContext->SetError(); + GetSessionManager()->DestroySessionContext(sessionContext); + MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); + MOT_LOG_DEBUG("%s - Exiting", threadName); + return; + } + uint16_t threadId = MOTCurrThreadId; if (GetGlobalConfiguration().m_enableNuma && !GetTaskAffinity().SetAffinity(threadId)) { MOT_LOG_WARN("Failed to set affinity for checkpoint worker, checkpoint performance may be affected"); } - Sentinel** deletedList = new (nothrow) Sentinel*[DELETE_LIST_SIZE]; - if (!deletedList) { + DeletePair* deletedList = new (std::nothrow) DeletePair[DELETE_LIST_SIZE]; + if (deletedList == nullptr) { MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to allocate memory for deleted sentinel list"); m_cpManager.OnError(ErrCodes::MEMORY, "Memory allocation failure"); + workerContext->SetError(); GetSessionManager()->DestroySessionContext(sessionContext); MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); - MOT_LOG_DEBUG("thread exiting"); + MOT_LOG_DEBUG("%s - Exiting", threadName); return; } - while (true) { - uint32_t tableId = 0; - uint64_t exId = 0; - uint32_t maxSegId = 0; - bool taskSucceeded = false; + workerContext->SetReady(); - if (m_cpManager.ShouldStop()) { + bool taskSucceeded = true; + while (taskSucceeded) { + if (m_cpManager.GetThreadNotifier().Wait(Wakeup, &m_cpManager) == ThreadNotifier::ThreadState::TERMINATE) { break; } - Table* table = GetTask(); - if (table != nullptr) { - do { - tableId = table->GetTableId(); - exId = table->GetTableExId(); + if (!buffer.IsAllocated()) { + if (!buffer.Initialize()) { + MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to initialize buffer"); + m_cpManager.OnError(ErrCodes::MEMORY, "Memory allocation failure"); + workerContext->SetError(); + break; + } + } + Table* table = GetTask(); + while (table != nullptr) { + uint32_t maxSegId = 0; + + do { + // Try to clean previous garbage if any. + gcSession->GcEndTxn(); + taskSucceeded = false; + uint32_t tableId = table->GetTableId(); ErrCodes errCode = WriteTableMetadataFile(table); if (errCode != ErrCodes::SUCCESS) { MOT_LOG_ERROR( - "CheckpointWorkerPool::WorkerFunc: failed to write table metadata file for table %u", tableId); + "CheckpointWorkerPool::WorkerFunc: Failed to write table metadata file for table %u", tableId); m_cpManager.OnError( errCode, "Failed to write table metadata file for table - ", std::to_string(tableId).c_str()); + workerContext->SetError(); break; } struct timespec start, end; uint64_t numOps = 0; - clock_gettime(CLOCK_MONOTONIC, &start); + (void)clock_gettime(CLOCK_MONOTONIC, &start); errCode = WriteTableDataFile(table, &buffer, deletedList, gcSession, threadId, maxSegId, numOps); if (errCode != ErrCodes::SUCCESS) { - MOT_LOG_ERROR( - "CheckpointWorkerPool::WorkerFunc: failed to write table data file for table %u", tableId); + MOT_LOG_ERROR("CheckpointWorkerPool::WorkerFunc: Failed to write table data file for table %u, " + "error: %u", + tableId, + errCode); m_cpManager.OnError( errCode, "Failed to write table data file for table - ", std::to_string(tableId).c_str()); + workerContext->SetError(); break; } taskSucceeded = true; - clock_gettime(CLOCK_MONOTONIC, &end); + (void)clock_gettime(CLOCK_MONOTONIC, &end); /* * (*1000000) is to convert seconds to micro seconds and * (/1000) is to convert nano seconds to micro seconds */ uint64_t deltaUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000; MOT_LOG_DEBUG( - "CheckpointWorkerPool::WorkerFunc: checkpoint of table %u completed in %luus, (%lu elements)", + "CheckpointWorkerPool::WorkerFunc: Checkpoint of table %u completed in %luus, (%lu elements)", tableId, deltaUs, numOps); @@ -327,30 +392,31 @@ void CheckpointWorkerPool::WorkerFunc() if (!taskSucceeded) { break; } - } else { - break; + + table = GetTask(); } } + delete[] deletedList; GetSessionManager()->DestroySessionContext(sessionContext); MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); - MOT_LOG_DEBUG("thread exiting"); + MOT_LOG_INFO("%s - Exiting", threadName); } bool CheckpointWorkerPool::BeginFile(int& fd, uint32_t tableId, int seg, uint64_t exId) { std::string fileName; - CheckpointUtils::MakeCpFilename(tableId, fileName, m_workingDir, seg); + CheckpointUtils::MakeCpFilename(tableId, fileName, m_cpManager.GetWorkingDir(), seg); if (!CheckpointUtils::OpenFileWrite(fileName, fd)) { MOT_LOG_ERROR("CheckpointWorkerPool::BeginFile: failed to create file: %s", fileName.c_str()); return false; } MOT_LOG_DEBUG("CheckpointWorkerPool::beginFile: %s", fileName.c_str()); - CheckpointUtils::FileHeader fileHeader{CP_MGR_MAGIC, tableId, exId, 0}; - if (CheckpointUtils::WriteFile(fd, (char*)&fileHeader, sizeof(CheckpointUtils::FileHeader)) != + CheckpointUtils::FileHeader fileHeader{CheckpointUtils::HEADER_MAGIC, tableId, exId, 0}; + if (CheckpointUtils::WriteFile(fd, (const char*)&fileHeader, sizeof(CheckpointUtils::FileHeader)) != sizeof(CheckpointUtils::FileHeader)) { MOT_LOG_ERROR("CheckpointWorkerPool::BeginFile: failed to write file header: %s", fileName.c_str()); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); fd = -1; return false; } @@ -365,8 +431,8 @@ bool CheckpointWorkerPool::FinishFile(int& fd, uint32_t tableId, uint64_t numOps MOT_LOG_ERROR("CheckpointWorkerPool::FinishFile: failed to seek in file (id: %u)", tableId); break; } - CheckpointUtils::FileHeader fileHeader{CP_MGR_MAGIC, tableId, exId, numOps}; - if (CheckpointUtils::WriteFile(fd, (char*)&fileHeader, sizeof(CheckpointUtils::FileHeader)) != + CheckpointUtils::FileHeader fileHeader{CheckpointUtils::HEADER_MAGIC, tableId, exId, numOps}; + if (CheckpointUtils::WriteFile(fd, (const char*)&fileHeader, sizeof(CheckpointUtils::FileHeader)) != sizeof(CheckpointUtils::FileHeader)) { MOT_LOG_ERROR("CheckpointWorkerPool::FinishFile: failed to write to file (id: %u)", tableId); break; @@ -386,14 +452,6 @@ bool CheckpointWorkerPool::FinishFile(int& fd, uint32_t tableId, uint64_t numOps return ret; } -bool CheckpointWorkerPool::SetCheckpointId() -{ - if (!CheckpointManager::CreateCheckpointId(m_checkpointId)) - return false; - MOT_LOG_DEBUG("CheckpointId is %lu", m_checkpointId); - return true; -} - CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableMetadataFile(Table* table) { uint32_t tableId = table->GetTableId(); @@ -410,7 +468,7 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableMetadataFile(Tabl table->Serialize(tableBuf); std::string fileName; - CheckpointUtils::MakeMdFilename(tableId, fileName, m_workingDir); + CheckpointUtils::MakeMdFilename(tableId, fileName, m_cpManager.GetWorkingDir()); if (!CheckpointUtils::OpenFileWrite(fileName, fd)) { MOT_LOG_ERROR("CheckpointWorkerPool::WriteTableMetadata: failed to create md file: %s", fileName.c_str()); @@ -418,20 +476,20 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableMetadataFile(Tabl return ErrCodes::FILE_IO; } - CheckpointUtils::MetaFileHeader mFileHeader; - mFileHeader.m_fileHeader.m_magic = CP_MGR_MAGIC; + CheckpointUtils::MetaFileHeader mFileHeader = {}; + mFileHeader.m_fileHeader.m_magic = CheckpointUtils::HEADER_MAGIC; mFileHeader.m_fileHeader.m_tableId = tableId; mFileHeader.m_fileHeader.m_exId = exId; - mFileHeader.m_entryHeader.m_dataLen = tableSize; + mFileHeader.m_entryHeader.m_base.m_dataLen = tableSize; - if (CheckpointUtils::WriteFile(fd, (char*)&mFileHeader, sizeof(CheckpointUtils::MetaFileHeader)) != + if (CheckpointUtils::WriteFile(fd, (const char*)&mFileHeader, sizeof(CheckpointUtils::MetaFileHeader)) != sizeof(CheckpointUtils::MetaFileHeader)) { MOT_LOG_ERROR("CheckpointWorkerPool::WriteTableMetadata: failed to write to md file: %s", fileName.c_str()); delete[] tableBuf; return ErrCodes::FILE_IO; } - if (CheckpointUtils::WriteFile(fd, tableBuf, tableSize) != tableSize) { + if (CheckpointUtils::WriteFile(fd, (const char*)tableBuf, tableSize) != tableSize) { MOT_LOG_ERROR("CheckpointWorkerPool::WriteTableMetadata: failed to write to md file: %s", fileName.c_str()); delete[] tableBuf; return ErrCodes::FILE_IO; @@ -456,7 +514,7 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableMetadataFile(Tabl bool CheckpointWorkerPool::FlushBuffer(int fd, Buffer* buffer) { if (buffer->Size() > 0) { // there is data in the buffer that needs to be written - if (CheckpointUtils::WriteFile(fd, (char*)buffer->Data(), buffer->Size()) != buffer->Size()) { + if (CheckpointUtils::WriteFile(fd, (const char*)buffer->Data(), buffer->Size()) != buffer->Size()) { return false; } buffer->Reset(); @@ -464,8 +522,29 @@ bool CheckpointWorkerPool::FlushBuffer(int fd, Buffer* buffer) return true; } +bool CheckpointWorkerPool::KeepIterating( + Index* index, IndexIterator*& it, uint32_t& numIterations, uint16_t threadId, GcManager* gcSession, ErrCodes& err) +{ + err = ErrCodes::SUCCESS; + if (it->IsValid() && numIterations >= MAX_ITERS_COUNT) { + MaxKey lastKey; + lastKey.CpKey(*(const Key*)(it->GetKey())); + delete it; + gcSession->GcReinitEpoch(); + bool found = false; + it = index->Search(&lastKey, true, true, threadId, found); + if (it == nullptr) { + MOT_LOG_ERROR("CheckpointWorkerPool::KeepIterating: failed to obtain iterator"); + err = ErrCodes::MEMORY; + return false; + } + numIterations = 0; + } + return it->IsValid(); +} + CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableDataFile(Table* table, Buffer* buffer, - Sentinel** deletedList, GcManager* gcSession, uint16_t threadId, uint32_t& maxSegId, uint64_t& numOps) + DeletePair* deletedList, GcManager* gcSession, uint16_t threadId, uint32_t& maxSegId, uint64_t& numOps) { uint32_t tableId = table->GetTableId(); uint64_t exId = table->GetTableExId(); @@ -473,6 +552,7 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableDataFile(Table* t uint16_t deletedListLocation = 0; uint64_t currFileOps = 0; uint32_t curSegLen = 0; + uint32_t numIterations = 0; maxSegId = 0; numOps = 0; @@ -482,10 +562,16 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableDataFile(Table* t return ErrCodes::INDEX; } + if (gcSession->GcStartTxn() != RC_OK) { + MOT_LOG_ERROR("CheckpointWorkerPool::WriteTableDataFile: initial GcStartTxn failed"); + return ErrCodes::MEMORY; + } + IndexIterator* it = index->Begin(threadId); if (it == nullptr) { MOT_LOG_ERROR( "CheckpointWorkerPool::WriteTableDataFile: failed to get iterator for primary index on table %u", tableId); + gcSession->GcEndTxn(); return ErrCodes::INDEX; } @@ -493,28 +579,37 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableDataFile(Table* t MOT_LOG_ERROR( "CheckpointWorkerPool::WriteTableDataFile: failed to create data file %u for table %u", maxSegId, tableId); delete it; + gcSession->GcEndTxn(); return ErrCodes::FILE_IO; } - + gcSession->SetGcType(GcManager::GC_TYPE::GC_CHECKPOINT); ErrCodes errCode = ErrCodes::SUCCESS; + bool executeGcTxnFailure = false; bool isDeleted = false; - while (it->IsValid()) { - MOT::Sentinel* sentinel = it->GetPrimarySentinel(); + bool needGc = false; + Row* deletedVersion = nullptr; + while (KeepIterating(index, it, numIterations, threadId, gcSession, errCode)) { + MOT::PrimarySentinel* sentinel = static_cast(it->GetPrimarySentinel()); MOT_ASSERT(sentinel); if (sentinel == nullptr) { MOT_LOG_ERROR("CheckpointWorkerPool::WriteTableDataFile: encountered a null sentinel"); it->Next(); continue; } - int ckptStatus = Checkpoint(buffer, sentinel, fd, threadId, isDeleted); - if (isDeleted) { - deletedList[deletedListLocation++] = sentinel; - ExecuteMicroGcTransaction(deletedList, gcSession, table, deletedListLocation, DELETE_LIST_SIZE); + int ckptStatus = Checkpoint(buffer, sentinel, fd, threadId, isDeleted, deletedVersion); + needGc = (isDeleted and !executeGcTxnFailure); + if (needGc) { + if (!ExecuteMicroGcTransaction(deletedList, gcSession, table, deletedListLocation, DELETE_LIST_SIZE)) { + executeGcTxnFailure = true; + } + deletedList[deletedListLocation].first = sentinel; + deletedList[deletedListLocation].second = deletedVersion; + deletedListLocation++; } if (ckptStatus == 1) { currFileOps++; curSegLen += table->GetTupleSize() + sizeof(CheckpointUtils::EntryHeader); - if (m_checkpointSegsize > 0 && curSegLen >= m_checkpointSegsize) { + if (curSegLen >= m_checkpointSegsize) { if (!FlushBuffer(fd, buffer)) { MOT_LOG_ERROR( "CheckpointWorkerPool::WriteTableDataFile: failed to write remaining buffer data (%u bytes) to " @@ -553,20 +648,23 @@ CheckpointWorkerPool::ErrCodes CheckpointWorkerPool::WriteTableDataFile(Table* t errCode = ErrCodes::CALC; break; } + numIterations++; it->Next(); } delete it; it = nullptr; - if (deletedListLocation > 0) { - MOT_LOG_DEBUG("Checkpoint worker clean leftovers, Table %s deletedListLocation = %u\n", + // Clean leftovers (if any). + needGc = (deletedListLocation > 0 && !executeGcTxnFailure); + if (needGc) { + MOT_LOG_DEBUG("Checkpoint worker clean leftovers, Table %s deletedListLocation = %u", table->GetTableName().c_str(), deletedListLocation); - ExecuteMicroGcTransaction(deletedList, gcSession, table, deletedListLocation, deletedListLocation); + (void)ExecuteMicroGcTransaction(deletedList, gcSession, table, deletedListLocation, deletedListLocation); } + gcSession->GcEndTxn(); table->ClearThreadMemoryCache(); - if (errCode != ErrCodes::SUCCESS) { if (fd != -1) { (void)CheckpointUtils::CloseFile(fd); diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.h b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.h index e299ac5ed..b2b13523a 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.h +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_worker.h @@ -28,16 +28,18 @@ #include #include #include +#include #include #include #include +#include #include "global.h" #include "buffer.h" #include "mm_gc_manager.h" +#include "thread_utils.h" namespace MOT { -const int CHECKPOINT_BUFFER_SIZE = 4096 * 1000; - +using DeletePair = std::pair; /** * @class CheckpointManagerCallbacks * @brief This class describes the interface for callback methods @@ -45,6 +47,11 @@ const int CHECKPOINT_BUFFER_SIZE = 4096 * 1000; */ class CheckpointManagerCallbacks { public: + /** + * @brief Returns the configured number of checkpoint workers. + */ + virtual uint32_t GetNumWorkers() const = 0; + /** * @brief Checkpoint task completion callback * @param checkpointId The checkpoint's id. @@ -55,10 +62,19 @@ public: virtual void TaskDone(Table* table, uint32_t numSegs, bool success) = 0; /** - * @brief Checks if the thread should terminate it work - * @return True if the the thread should stop. + * @brief returns the current in progress checkpoint working dir. */ - virtual bool ShouldStop() const = 0; + virtual std::string& GetWorkingDir() = 0; + + /** + * @brief returns the tasks list. + */ + virtual std::list& GetTasksList() = 0; + + /** + * @brief returns the current NA bit. + */ + virtual bool GetNotAvailableBit() const = 0; /** * @brief Checkpoint task error callback @@ -68,6 +84,8 @@ public: */ virtual void OnError(int errCode, const char* errMsg, const char* optionalMsg = nullptr) = 0; + virtual ThreadNotifier& GetThreadNotifier() = 0; + virtual ~CheckpointManagerCallbacks() {} }; @@ -78,34 +96,33 @@ public: */ class CheckpointWorkerPool { public: - CheckpointWorkerPool(int n, bool b, std::list& l, uint32_t s, uint64_t id, CheckpointManagerCallbacks& m) - : m_numWorkers(n), m_tasksList(l), m_checkpointId(id), m_na(b), m_cpManager(m), m_checkpointSegsize(s) - { - Start(); - } + CheckpointWorkerPool(CheckpointManagerCallbacks& cbs, uint32_t segSize) + : m_cpManager(cbs), m_checkpointSegsize(segSize) + {} ~CheckpointWorkerPool(); - void Start(); + bool Start(); enum ErrCodes { SUCCESS = 0, FILE_IO = 1, MEMORY = 2, TABLE = 3, INDEX = 4, CALC = 5 }; - static constexpr uint16_t DELETE_LIST_SIZE = 1000; + static constexpr uint16_t MAX_ITERS_COUNT = 10000; private: /** - * @brief The main worker function + * @brief The main worker function. */ - void WorkerFunc(); + void WorkerFunc(uint32_t workerId); /** * @brief Appends a row to the buffer. The buffer will be flushed in case it is full. * @param buffer The buffer to fill. * @param row The row to write. * @param fd The file descriptor to write to. + * @param the row's transaction id. * @return Boolean value denoting success or failure. */ - bool Write(Buffer* buffer, Row* row, int fd); + bool Write(Buffer* buffer, Row* row, int fd, uint64_t transactionId); /** * @brief Checkpoints a row, according to whether a stable version exists or not. @@ -116,7 +133,8 @@ private: * @param isDeleted The row delete status. * @return -1 on error, 0 if nothing was written and 1 if the row was written. */ - int Checkpoint(Buffer* buffer, Sentinel* sentinel, int fd, uint16_t threadId, bool& isDeleted); + int Checkpoint( + Buffer* buffer, PrimarySentinel* sentinel, int fd, uint16_t threadId, bool& isDeleted, Row*& deletedVersion); /** * @brief Pops a task (table pointer) from the tasks queue. @@ -124,12 +142,6 @@ private: */ Table* GetTask(); - /** - * @brief Creates a checkpoint id for the current checkpoint - * @return Boolean value denoting success or failure. - */ - bool SetCheckpointId(); - /** * @brief Initializes a checkpoint file * @param fd The returned file descriptor of the file. @@ -150,8 +162,8 @@ private: */ bool FinishFile(int& fd, uint32_t tableId, uint64_t numOps, uint64_t exId); - void ExecuteMicroGcTransaction( - Sentinel** deletedList, GcManager* gcSession, Table* table, uint16_t& deletedCounter, uint16_t limit); + bool ExecuteMicroGcTransaction( + DeletePair* deletedList, GcManager* gcSession, Table* table, uint16_t& deletedCounter, uint16_t limit); /** * @brief Writes table metadata to the metadata file. @@ -171,31 +183,32 @@ private: * @param numOps The number of rows written. * @return Returns the error code of type ErrCodes. */ - ErrCodes WriteTableDataFile(Table* table, Buffer* buffer, Sentinel** deletedList, GcManager* gcSession, + ErrCodes WriteTableDataFile(Table* table, Buffer* buffer, DeletePair* deletedList, GcManager* gcSession, uint16_t threadId, uint32_t& maxSegId, uint64_t& numOps); + /* @brief Checks whether we should continue to iterate on a table. + * Recreates the iterator when the number of iterations exceeds a threshold. + * @param index the index to iterate on. + * @param it iterators's pointer. + * @param numIterations The current number of iterations. + * @param threadId The thread id. + * @param gcSession GC manager object. + * @param err returned error code. + */ + bool KeepIterating(Index* index, IndexIterator*& it, uint32_t& numIterations, uint16_t threadId, + GcManager* gcSession, ErrCodes& err); + bool FlushBuffer(int fd, Buffer* buffer); - // Workers - void* m_workers; + // Worker thread contexts + std::vector m_workerContexts; - volatile std::atomic m_numWorkers; - - // Holds tables to checkpoint - std::list& m_tasksList; + // Workers threads + std::vector m_workers; // Guards tasksList pops std::mutex m_tasksLock; - // The directory in which checkpoint files will be saved in - std::string m_workingDir; - - // Checkpoint's id - uint64_t m_checkpointId; - - // The current NotAvailable bit - bool m_na; - // Checkpoint manager callbacks CheckpointManagerCallbacks& m_cpManager; diff --git a/src/gausskernel/storage/mot/core/system/common/connection_id.cpp b/src/gausskernel/storage/mot/core/system/common/connection_id.cpp index 6604717a6..a49f6a461 100644 --- a/src/gausskernel/storage/mot/core/system/common/connection_id.cpp +++ b/src/gausskernel/storage/mot/core/system/common/connection_id.cpp @@ -41,13 +41,13 @@ static pthread_spinlock_t connectionIdLock; static enum ConnectionIdInitPhase { INIT, LOCK_INIT, ARRAY_ALLOC, DONE } initPhase = INIT; // the bit index is counted from MSB[0] to LSB[63]! -#define CONNECTION_ID_BIT_AT(bitIndex) (((uint64_t)1) << (CONNECTIONS_PER_WORD - (bitIndex)-1)) +#define CONNECTION_ID_BIT_AT(bitIndex) (((uint64_t)1) << ((CONNECTIONS_PER_WORD - (bitIndex)) - 1)) #define IS_CONNECTION_ID_BIT_SET(arrayIndex, bitIndex) \ (connectionIdBitsetArray[arrayIndex] & CONNECTION_ID_BIT_AT(bitIndex)) #define RAISE_CONNECTION_ID_BIT(arrayIndex, bitIndex) \ - connectionIdBitsetArray[arrayIndex] |= CONNECTION_ID_BIT_AT(bitIndex) + (connectionIdBitsetArray[arrayIndex] |= CONNECTION_ID_BIT_AT(bitIndex)) #define CLEAR_CONNECTION_ID_BIT(arrayIndex, bitIndex) \ - connectionIdBitsetArray[arrayIndex] &= ~CONNECTION_ID_BIT_AT(bitIndex) + (connectionIdBitsetArray[arrayIndex] &= ~CONNECTION_ID_BIT_AT(bitIndex)) // pay attention: the bit set is inverted - "0" means occupied and "1" means free for usage #define MARK_CONNECTION_ID_USED(arrayIndex, bitIndex) CLEAR_CONNECTION_ID_BIT(arrayIndex, bitIndex) @@ -139,7 +139,7 @@ extern void DestroyConnectionIdPool() free(connectionIdBitsetArray); // fall through case LOCK_INIT: - pthread_spin_destroy(&connectionIdLock); + (void)pthread_spin_destroy(&connectionIdLock); // fall through default: break; @@ -151,7 +151,7 @@ extern ConnectionId AllocConnectionId() { ConnectionId result = INVALID_CONNECTION_ID; - pthread_spin_lock(&connectionIdLock); + (void)pthread_spin_lock(&connectionIdLock); for (uint32_t arrayIndex = 0; arrayIndex < connectionIdArraySize; ++arrayIndex) { if (connectionIdBitsetArray[arrayIndex] != 0) { uint32_t bitIndex = __builtin_clzll(connectionIdBitsetArray[arrayIndex]); @@ -169,7 +169,7 @@ extern ConnectionId AllocConnectionId() break; } } - pthread_spin_unlock(&connectionIdLock); + (void)pthread_spin_unlock(&connectionIdLock); if (result == INVALID_CONNECTION_ID) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -186,11 +186,11 @@ extern ConnectionId AllocConnectionIdHighest() { ConnectionId result = INVALID_CONNECTION_ID; - pthread_spin_lock(&connectionIdLock); + (void)pthread_spin_lock(&connectionIdLock); // since bit-set computation is tedious in case maxThreadCount is not a full multiple of 64, we do it in the // opposite way for (uint32_t i = 0; i < maxConnectionCount; ++i) { - ConnectionId connectionId = maxConnectionCount - i - 1; + ConnectionId connectionId = (maxConnectionCount - i) - 1; uint32_t arrayIndex = connectionId / CONNECTIONS_PER_WORD; uint32_t bitIndex = connectionId % CONNECTIONS_PER_WORD; if (IS_CONNECTION_ID_FREE(arrayIndex, bitIndex)) { @@ -199,7 +199,7 @@ extern ConnectionId AllocConnectionIdHighest() break; } } - pthread_spin_unlock(&connectionIdLock); + (void)pthread_spin_unlock(&connectionIdLock); if (result == INVALID_CONNECTION_ID) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -217,11 +217,11 @@ extern void FreeConnectionId(ConnectionId connectionId) uint32_t arrayIndex = connectionId / CONNECTIONS_PER_WORD; uint32_t bitIndex = connectionId % CONNECTIONS_PER_WORD; - pthread_spin_lock(&connectionIdLock); + (void)pthread_spin_lock(&connectionIdLock); MOT_ASSERT(IS_CONNECTION_ID_USED(arrayIndex, bitIndex)); MARK_CONNECTION_ID_FREE(arrayIndex, bitIndex); MOT_ATOMIC_DEC(currentConnectionCount); - pthread_spin_unlock(&connectionIdLock); + (void)pthread_spin_unlock(&connectionIdLock); MOT_LOG_TRACE("De-allocated internal connection id %u", connectionId); } diff --git a/src/gausskernel/storage/mot/core/system/common/connection_id.h b/src/gausskernel/storage/mot/core/system/common/connection_id.h index 5548437be..80568c177 100644 --- a/src/gausskernel/storage/mot/core/system/common/connection_id.h +++ b/src/gausskernel/storage/mot/core/system/common/connection_id.h @@ -25,7 +25,7 @@ #ifndef CONNECTION_ID_H #define CONNECTION_ID_H -#include +#include namespace MOT { // In Thread-Pooled environments we need a reusable connection id (not a running number like diff --git a/src/gausskernel/storage/mot/core/system/common/commit_sequence_number.cpp b/src/gausskernel/storage/mot/core/system/common/csn_manager.cpp similarity index 63% rename from src/gausskernel/storage/mot/core/system/common/commit_sequence_number.cpp rename to src/gausskernel/storage/mot/core/system/common/csn_manager.cpp index 0a90f16d5..bdd9cc97a 100644 --- a/src/gausskernel/storage/mot/core/system/common/commit_sequence_number.cpp +++ b/src/gausskernel/storage/mot/core/system/common/csn_manager.cpp @@ -23,8 +23,7 @@ */ #include -#include "commit_sequence_number.h" -#include "recovery_manager.h" +#include "csn_manager.h" #include "utilities.h" #include "mot_engine.h" @@ -34,34 +33,16 @@ DECLARE_LOGGER(CSNManager, System); CSNManager::CSNManager() : m_csn(INITIAL_CSN) {} -CSNManager::~CSNManager() -{} - -RC CSNManager::SetCSN(uint64_t value) +void CSNManager::SetCSN(uint64_t value) { if (!MOTEngine::GetInstance()->IsRecovering()) { MOT_LOG_ERROR("Set CSN is supported only during recovery"); - return RC_ERROR; + MOT_ASSERT(false); } else { - m_csn = value; - return RC_OK; - } -} - -uint64_t CSNManager::GetNextCSN() -{ - if (MOTEngine::GetInstance() && MOTEngine::GetInstance()->IsRecovering()) { - // Read queries on standby - MOT_LOG_DEBUG("CSN increment is not supported during recovery"); - return 0; - } else { - uint64_t current = 0; - uint64_t next = 0; - do { - current = m_csn; - next = current + 1; - } while (!m_csn.compare_exchange_strong(current, next, std::memory_order_acq_rel)); - return next; + // CAS is not needed as it used only by a single thread always. + if (m_csn.load() <= value) { + m_csn = value + 1; // GetNextCSN is fetch and then increment. + } } } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/common/commit_sequence_number.h b/src/gausskernel/storage/mot/core/system/common/csn_manager.h similarity index 63% rename from src/gausskernel/storage/mot/core/system/common/commit_sequence_number.h rename to src/gausskernel/storage/mot/core/system/common/csn_manager.h index 610579e48..3cf59b904 100644 --- a/src/gausskernel/storage/mot/core/system/common/commit_sequence_number.h +++ b/src/gausskernel/storage/mot/core/system/common/csn_manager.h @@ -13,11 +13,11 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * commit_sequence_number.h + * csn_manager.h * Encapsulates the logic of a global unique auto incremental number. * * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/common/commit_sequence_number.h + * src/gausskernel/storage/mot/core/system/common/csn_manager.h * * ------------------------------------------------------------------------- */ @@ -27,37 +27,53 @@ #include #include "global.h" +#include "icsn_manager.h" namespace MOT { + /** * @class CSNManager * @brief Encapsulates the logic of a global unique auto incremental number. The CSN is used to identify the order of * commit records where required (for example, during recovery). */ -class CSNManager { +class CSNManager final : public ICSNManager { public: /** @brief Constructor. */ CSNManager(); - CSNManager(const CSNManager& orig) = delete; - CSNManager& operator=(const CSNManager& orig) = delete; /** @brief Destructor. */ - virtual ~CSNManager(); - - /** @brief Used to enforce a csn value (used after recovery). */ - RC SetCSN(uint64_t value); + ~CSNManager() override + {} /** @brief Get the next csn. This method also increment the current csn. */ - uint64_t GetNextCSN(); + uint64_t GetNextCSN() override + { + uint64_t current = INVALID_CSN; + uint64_t next = INVALID_CSN; + do { + current = m_csn; + next = current + 1; + } while (!m_csn.compare_exchange_strong(current, next, std::memory_order_acq_rel)); + return current; + } /** @brief Get the current csn. This method does not change the value of the current csn. */ - inline uint64_t GetCurrentCSN() const + uint64_t GetCurrentCSN() override { return m_csn; } - static constexpr uint64_t INVALID_CSN = 0; // Equal to InvalidCommitSeqNo in the envelope. - static constexpr uint64_t INITIAL_CSN = 3; // Equal to COMMITSEQNO_FIRST_NORMAL in the envelope. + /** @brief Get the current csn. This method does not change the value of the current csn. */ + uint64_t GetGcEpoch() override + { + return m_csn; + } + + /** + * @brief Used to enforce a csn value. It is used only during recovery by the committer (single thread) or at the + * end of the recovery. + */ + void SetCSN(uint64_t value) override; private: /** @brief atomic uint64_t holds the current csn value */ diff --git a/src/gausskernel/storage/mot/core/system/common/gc_context.h b/src/gausskernel/storage/mot/core/system/common/gc_context.h index 33c221171..81e44f68c 100644 --- a/src/gausskernel/storage/mot/core/system/common/gc_context.h +++ b/src/gausskernel/storage/mot/core/system/common/gc_context.h @@ -36,15 +36,15 @@ namespace MOT { * @brief Global GC managers pool */ class GcContext { - public: GcContext(){}; ~GcContext() { - if (m_isInitialized == true) { + if (m_isInitialized) { delete[] m_privateHandler; } + m_privateHandler = nullptr; }; /** diff --git a/src/gausskernel/storage/mot/core/system/common/icsn_manager.h b/src/gausskernel/storage/mot/core/system/common/icsn_manager.h new file mode 100644 index 000000000..850e9a01e --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/common/icsn_manager.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * csn_manager.h + * CSN Manager interfaces. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/common/icsn_manager.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef ICSN_MANAGER_H +#define ICSN_MANAGER_H + +#include "global.h" + +namespace MOT { +class ICSNManager { +public: + ICSNManager(const ICSNManager& orig) = delete; + ICSNManager& operator=(const ICSNManager& orig) = delete; + + /** @brief Destructor. */ + virtual ~ICSNManager() + {} + + /** @brief Get the next csn. This method also increment the current csn. */ + virtual uint64_t GetNextCSN() = 0; + + /** @brief Get the current csn. This method does not change the value of the current csn. */ + virtual uint64_t GetCurrentCSN() = 0; + + /** @brief Get the current csn. This method does not change the value of the current csn. */ + virtual uint64_t GetGcEpoch() = 0; + + /** + * @brief Used to enforce a csn value. It is used only during recovery by the committer (single thread) or at the + * end of the recovery. + */ + virtual void SetCSN(uint64_t value) = 0; + +protected: + ICSNManager() + {} +}; +} // namespace MOT + +#endif /* ICSN_MANAGER_H */ diff --git a/src/gausskernel/storage/mot/core/system/common/session_context.cpp b/src/gausskernel/storage/mot/core/system/common/session_context.cpp index 17dd6f341..086d5569a 100644 --- a/src/gausskernel/storage/mot/core/system/common/session_context.cpp +++ b/src/gausskernel/storage/mot/core/system/common/session_context.cpp @@ -32,7 +32,7 @@ #include "kvthread.hh" #include -#include +#include #include namespace MOT { @@ -42,43 +42,65 @@ std::atomic SessionContext::m_nextSessionId(0); static int GetRealCurrentNumaMode() { - int node = 0; // We default to Node 0 to avoid failures in other places in the code. + // We default to Node 0 to avoid failures in other places in the code. + int node = 0; if (GetGlobalConfiguration().m_enableNuma) { int cpu = sched_getcpu(); + if (cpu < 0) { + MOT_REPORT_SYSTEM_ERROR(sched_getcpu, "Initialize Session", "Failed to get current CPU id"); + return -1; + } node = MotSysNumaGetNode(cpu); + if (node < 0) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Initialize Session", "Failed to get NUMA node for CPU %d", cpu); + return -1; + } } return node; } -extern void InitCurrentNumaNodeId() +extern bool InitCurrentNumaNodeId() { - if (MOTCurrentNumaNodeId == MEM_INVALID_NODE) { - if (MOTCurrThreadId == INVALID_THREAD_ID) { - MOT_LOG_ERROR( - "Invalid attempt to initialize current NUMA node identifier without current thread identifier denied"); - SetLastError(MOT_ERROR_INTERNAL, MOT_SEVERITY_ERROR); - } else { - if (IS_AFFINITY_ACTIVE(GetSessionAffinity().GetAffinityMode())) { - MOTCurrentNumaNodeId = GetSessionAffinity().GetAffineNuma(MOTCurrThreadId); - MOT_LOG_TRACE("Initialized current NUMA node identifier to %d for thread %" PRIu16 " by affinity map", - MOTCurrentNumaNodeId, - MOTCurrThreadId); - } else { - MOTCurrentNumaNodeId = GetRealCurrentNumaMode(); - MOT_LOG_TRACE("Initialized current NUMA node identifier to %d for thread %" PRIu16 - " by kernel info (pthread: %" PRIu64 ")", - MOTCurrentNumaNodeId, - MOTCurrThreadId, - (uint64_t)pthread_self()); - } - } - } else { + if (MOTCurrentNumaNodeId != MEM_INVALID_NODE) { MOT_LOG_TRACE( "Ignoring double attempt to initialize current NUMA node identifier for current thread (thread: %" PRIu16 ", node: %d)", MOTCurrThreadId, MOTCurrentNumaNodeId); + return true; } + + if (MOTCurrThreadId == INVALID_THREAD_ID) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Init Engine", + "Invalid attempt to initialize current NUMA node identifier without current thread identifier denied"); + return false; + } + + if (IS_AFFINITY_ACTIVE(GetSessionAffinity().GetAffinityMode())) { + int nodeId = GetSessionAffinity().GetAffineNuma(MOTCurrThreadId); + if (nodeId == -1) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Init Engine", "Failed to get NUMA node id for current thread"); + return false; + } + MOTCurrentNumaNodeId = nodeId; + MOT_LOG_TRACE("Initialized current NUMA node identifier to %d for thread %" PRIu16 " by affinity map", + MOTCurrentNumaNodeId, + MOTCurrThreadId); + } else { + int nodeId = GetRealCurrentNumaMode(); + if (nodeId == -1) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Init Engine", "Failed to get real NUMA node id"); + return false; + } + MOTCurrentNumaNodeId = nodeId; + MOT_LOG_TRACE("Initialized current NUMA node identifier to %d for thread %" PRIu16 + " by kernel info (pthread: %" PRIu64 ")", + MOTCurrentNumaNodeId, + MOTCurrThreadId, + (uint64_t)pthread_self()); + } + return true; } extern void ClearCurrentNumaNodeId() @@ -114,13 +136,27 @@ extern void DestroyMasstreeThreadinfo() mtSessionThreadInfo, MOTCurrThreadId); - threadinfo::make(mtSessionThreadInfo, threadinfo::TI_PROCESS, MOTCurrThreadId, -1 /* Destroy object */); + (void)threadinfo::make(mtSessionThreadInfo, threadinfo::TI_PROCESS, MOTCurrThreadId, -1 /* Destroy object */); mtSessionThreadInfo = nullptr; } } -SessionContext::SessionContext(SessionId sessionId, ConnectionId connectionId, void* userData) - : m_sessionId(sessionId), m_connectionId(connectionId), m_refCount(1), m_txn(nullptr) +void SessionContext::SetTxnContext(TxnManager* txn) +{ + if (txn == nullptr) { + return; + } + + txn->SetThdId(MOTCurrThreadId); + txn->SetConnectionId(MOT_GET_CURRENT_CONNECTION_ID()); + if (u_sess != nullptr) { + u_sess->mot_cxt.txn_manager = txn; + } +} + +SessionContext::SessionContext( + SessionId sessionId, ConnectionId connectionId, void* userData, bool isMtls /* = false */) + : m_sessionId(sessionId), m_connectionId(connectionId), m_refCount(1), m_txn(nullptr), m_isMtls(isMtls ? 1 : 0) { MOT_LOG_TRACE("Session Id: %u, Connection Id: %u, NUMA node id: %d, thread id: %u", m_sessionId, @@ -132,6 +168,7 @@ SessionContext::SessionContext(SessionId sessionId, ConnectionId connectionId, v SessionContext::~SessionContext() { if (m_txn != nullptr) { + MOT_ASSERT(!m_isMtls); MemSessionFreeObject(m_txn); m_txn = nullptr; } @@ -139,19 +176,23 @@ SessionContext::~SessionContext() bool SessionContext::Init(bool isLightSession) { - // ATTENTION: using internal thread id for NUMA affinity - MOTThreadId threadId = MOTCurrThreadId; - MOT_LOG_DEBUG("txn_man::init - memory pools allocated for threadId=%u", (unsigned)threadId); - m_txn = CreateTransaction(this, threadId, m_connectionId, isLightSession); - if (m_txn == nullptr) { - MOT_LOG_ERROR( - "Failed to create transaction manager for session %u on thread %u", m_sessionId, (unsigned)threadId); - return false; + if (!m_isMtls) { + // ATTENTION: using internal thread id for NUMA affinity + MOTThreadId threadId = MOTCurrThreadId; + MOT_LOG_DEBUG("SessionContext::Init - memory pools allocated for threadId=%u", (unsigned)threadId); + m_txn = CreateTransaction(this, threadId, m_connectionId, isLightSession); + if (m_txn == nullptr) { + MOT_LOG_ERROR( + "Failed to create transaction manager for session %u on thread %u", m_sessionId, (unsigned)threadId); + return false; + } } if (!InitMasstreeThreadinfo()) { - MemSessionFreeObject(m_txn); - m_txn = nullptr; + if (!m_isMtls) { + MemSessionFreeObject(m_txn); + m_txn = nullptr; + } return false; } @@ -173,15 +214,15 @@ TxnManager* SessionContext::CreateTransaction( // Do it before txn_man::init call since it allocates memory bool rc = true; - if (isLightTxn == false) { + if (!isLightTxn) { if (GetGlobalConfiguration().m_enableNuma && IS_AFFINITY_ACTIVE(GetSessionAffinity().GetAffinityMode())) { if (!GetSessionAffinity().SetAffinity(threadId)) { MOT_LOG_WARN("Failed to set current session affinity, performance may be affected"); } } - rc = txnManager->Init(threadId, connectionId); + rc = txnManager->Init(threadId, connectionId, false); } else { - rc = txnManager->Init(threadId, connectionId, true); + rc = txnManager->Init(threadId, connectionId, false, true); } if (unlikely(!rc)) { diff --git a/src/gausskernel/storage/mot/core/system/common/session_context.h b/src/gausskernel/storage/mot/core/system/common/session_context.h index 218b2b626..5d7136eaa 100644 --- a/src/gausskernel/storage/mot/core/system/common/session_context.h +++ b/src/gausskernel/storage/mot/core/system/common/session_context.h @@ -60,8 +60,9 @@ private: * @param sessionId The unique session identifier. * @param sessionId The reusable connection identifier. * @param userData Session-level envelope data. + * @param isMtls Whether it is an MTLS thread's session context. */ - SessionContext(SessionId sessionId, ConnectionId connectionId, void* userData); + SessionContext(SessionId sessionId, ConnectionId connectionId, void* userData, bool isMtls = false); /** @brief Destructor. */ ~SessionContext(); @@ -131,6 +132,13 @@ public: return m_txn; } + static void SetTxnContext(TxnManager* txn); + + inline bool IsMtlsRecoverySession() const + { + return (bool)m_isMtls; + } + private: /** @brief Creates a transaction object. */ static TxnManager* CreateTransaction( @@ -157,8 +165,11 @@ private: /** @var The (reusable) transaction object associated with the session. */ TxnManager* m_txn; // L1 offset 24-32 + /** @var parallel recovery thread indication. */ + uint8_t m_isMtls; // L1 offset 32-33 + /** @var Align class size to L1 cache line. */ - uint8_t m_padding3[32]; // L1 offset 32-64 + uint8_t m_padding3[31]; // L1 offset 33-64 }; /** @define Thread-local quick access to current session identifier. */ @@ -174,7 +185,7 @@ private: #define MOT_SET_CURRENT_SESSION_CONTEXT(sessionContext) u_sess->mot_cxt.session_context = sessionContext; /** @brief Initializes the current NUMA node identifier TLS. */ -extern void InitCurrentNumaNodeId(); +extern bool InitCurrentNumaNodeId(); /** @brief Clears the current NUMA node identifier TLS. */ extern void ClearCurrentNumaNodeId(); diff --git a/src/gausskernel/storage/mot/core/system/common/session_manager.cpp b/src/gausskernel/storage/mot/core/system/common/session_manager.cpp index 58073a90f..3a07372b0 100644 --- a/src/gausskernel/storage/mot/core/system/common/session_manager.cpp +++ b/src/gausskernel/storage/mot/core/system/common/session_manager.cpp @@ -32,6 +32,7 @@ #include "mm_numa.h" #include "knl/knl_thread.h" +#include "knl/knl_session.h" namespace MOT { DECLARE_LOGGER(SessionManager, System) @@ -123,7 +124,7 @@ void SessionManager::CleanupFailedSessionContext( SessionContext* SessionManager::CreateSessionContext(bool isLightSession /* = false */, uint64_t reserveMemoryKb /* = 0 */, void* userData /* = nullptr */, - ConnectionId connectionId /* = INVALID_CONNECTION_ID */) + ConnectionId connectionId /* = INVALID_CONNECTION_ID */, bool isMtls /* = false */) { // reject request if a session was already defined for the current thread SessionContext* sessionContext = MOT_GET_CURRENT_SESSION_CONTEXT(); @@ -140,7 +141,6 @@ SessionContext* SessionManager::CreateSessionContext(bool isLightSession /* = fa SessionId sessionId = INVALID_SESSION_ID; do { // instead of goto - // phase 1: setup all identifiers MOTThreadId threadId = AllocThreadId(); if (threadId == INVALID_THREAD_ID) { @@ -183,7 +183,11 @@ SessionContext* SessionManager::CreateSessionContext(bool isLightSession /* = fa // although this is thread-level attribute (even in a thread-pooled envelope) we still initialize it on-demand // whenever a new session starts, since we do not have a "on-thread-started" event (and delivering such an event // in the correct timing could be difficult, cumbersome and eventually counter-productive) - InitCurrentNumaNodeId(); + if (!InitCurrentNumaNodeId()) { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Create Session Context", "Failed to initializes NUMA node identifier"); + break; + } MOT_LOG_TRACE("Creating session for thread id %" PRIu16 ", connection id %u, node id %u (light session: %s, memory reservation: %" PRIu64 " KB)", threadId, @@ -240,7 +244,7 @@ SessionContext* SessionManager::CreateSessionContext(bool isLightSession /* = fa break; } - sessionContext = new (inplaceBuffer) SessionContext(sessionId, connectionId, userData); + sessionContext = new (inplaceBuffer) SessionContext(sessionId, connectionId, userData, isMtls); MOT_LOG_TRACE("Created new session %u", sessionContext->GetSessionId()); initPhase = ALLOC_SESSION_BUFFER_PHASE; @@ -252,9 +256,11 @@ SessionContext* SessionManager::CreateSessionContext(bool isLightSession /* = fa sessionContext->GetSessionId()); break; } - m_sessionContextMap.insert(sessionContext->GetSessionId(), sessionContext); - // Add Garbage collection to active_list - sessionContext->GetTxnManager()->GcAddSession(); + (void)m_sessionContextMap.insert(sessionContext->GetSessionId(), sessionContext); + if (!isMtls) { + // Add Garbage collection to active_list + sessionContext->GetTxnManager()->GcAddSession(); + } initPhase = INIT_SESSION_PHASE; // phase 5: reserve statistics thread slot @@ -296,7 +302,7 @@ void SessionManager::DestroySessionContext(SessionContext* sessionContext) "Ignoring attempt to delete mismatching session context (attempting to delete from another thread?)"); return; } - + bool isMtls = sessionContext->IsMtlsRecoverySession(); MOT_LOG_DEBUG("deleteSessionContext(): Session %u for thread id %" PRIu16 ", connection %u: ref-cnt=%" PRIu64, sessionContext->GetSessionId(), MOTCurrThreadId, @@ -305,10 +311,14 @@ void SessionManager::DestroySessionContext(SessionContext* sessionContext) if (sessionContext->DecRefCount() == 0) { ConnectionId connectionId = sessionContext->GetConnectionId(); SessionId sessionId = sessionContext->GetSessionId(); - MOT_LOG_TRACE("Destroying session %u, connection %u with surrogate key %" PRIu64, - sessionId, - connectionId, - sessionContext->GetTxnManager()->GetSurrogateCount()); + if (!isMtls) { + MOT_LOG_TRACE("Destroying session %u, connection %u with surrogate key %" PRIu64, + sessionId, + connectionId, + sessionContext->GetTxnManager()->GetSurrogateCount()); + } else { + MOT_LOG_TRACE("Destroying session %u, connection %u (mtls)", sessionId, connectionId); + } #ifdef MEM_SESSION_ACTIVE MemSessionPrintStats(connectionId, "Pre-Shutdown report", LogLevel::LL_TRACE); @@ -326,11 +336,12 @@ void SessionManager::DestroySessionContext(SessionContext* sessionContext) if (!m_sessionContextMap.remove(sessionId)) { MOT_LOG_WARN("Failed to remove session %u from global session map - not found", sessionId); } - - GetSurrogateKeyManager()->SetSurrogateSlot(connectionId, sessionContext->GetTxnManager()->GetSurrogateCount()); - - // Remove from link list - sessionContext->GetTxnManager()->GcRemoveSession(); + if (!isMtls) { + GetSurrogateKeyManager()->SetSurrogateSlot( + connectionId, sessionContext->GetTxnManager()->GetSurrogateCount()); + // Remove from link list + sessionContext->GetTxnManager()->GcRemoveSession(); + } sessionContext->~SessionContext(); #ifdef MEM_SESSION_ACTIVE @@ -370,23 +381,10 @@ ScopedSessionManager::ScopedSessionManager() knl_thread_mot_init(); // initialize session members - errno_t erc = memset_s(&m_localUSess, sizeof(knl_session_context), 0, sizeof(knl_session_context)); + errno_t erc = + memset_s(static_cast(&m_localUSess), sizeof(knl_session_context), 0, sizeof(knl_session_context)); securec_check(erc, "\0", "\0"); - m_localUSess.mot_cxt.callbacks_set = false; - m_localUSess.mot_cxt.session_id = INVALID_SESSION_ID; - m_localUSess.mot_cxt.connection_id = INVALID_CONNECTION_ID; - m_localUSess.mot_cxt.session_context = nullptr; - m_localUSess.mot_cxt.txn_manager = nullptr; - m_localUSess.mot_cxt.jit_session_context_pool = nullptr; - m_localUSess.mot_cxt.jit_context_count = 0; - m_localUSess.mot_cxt.jit_llvm_if_stack = nullptr; - m_localUSess.mot_cxt.jit_llvm_while_stack = nullptr; - m_localUSess.mot_cxt.jit_llvm_do_while_stack = nullptr; - m_localUSess.mot_cxt.jit_tvm_if_stack = nullptr; - m_localUSess.mot_cxt.jit_tvm_while_stack = nullptr; - m_localUSess.mot_cxt.jit_tvm_do_while_stack = nullptr; - m_localUSess.mot_cxt.jit_context = nullptr; - m_localUSess.mot_cxt.jit_txn = nullptr; + knl_u_mot_init(&m_localUSess.mot_cxt); // setup the session context u_sess = &m_localUSess; diff --git a/src/gausskernel/storage/mot/core/system/common/session_manager.h b/src/gausskernel/storage/mot/core/system/common/session_manager.h index 710e50625..9e65e3b04 100644 --- a/src/gausskernel/storage/mot/core/system/common/session_manager.h +++ b/src/gausskernel/storage/mot/core/system/common/session_manager.h @@ -59,11 +59,12 @@ public: * @param[opt] reserveMemoryKb Specify a memory reservation for the session in kilo bytes. * Pass zero to use default session reservation (see workMemoryMaxLimitKB in mot_configuration.h). * @param[opt] userData User data associated with the session. + * @param[opt] is this an MTLS recovery session. * @return The new session context. * @see @ref workMemoryMaxLimitKB) */ SessionContext* CreateSessionContext(bool isLightSession = false, uint64_t reserveMemoryKb = 0, - void* userData = nullptr, ConnectionId connectionId = INVALID_CONNECTION_ID); + void* userData = nullptr, ConnectionId connectionId = INVALID_CONNECTION_ID, bool isMtls = false); /** * @brief Destroys a session context. @@ -99,8 +100,12 @@ public: { GcManager* result = nullptr; SessionContext* sessionContext = MOT_GET_CURRENT_SESSION_CONTEXT(); - if (sessionContext != nullptr) { + if (sessionContext != nullptr && !sessionContext->IsMtlsRecoverySession()) { result = sessionContext->GetTxnManager()->GetGcSession(); + } else { + if (u_sess->mot_cxt.txn_manager != nullptr) { + result = u_sess->mot_cxt.txn_manager->GetGcSession(); + } } return result; } diff --git a/src/gausskernel/storage/mot/core/system/common/table_manager.cpp b/src/gausskernel/storage/mot/core/system/common/table_manager.cpp index 220ef240c..c6241ffb0 100644 --- a/src/gausskernel/storage/mot/core/system/common/table_manager.cpp +++ b/src/gausskernel/storage/mot/core/system/common/table_manager.cpp @@ -50,13 +50,13 @@ bool TableManager::AddTable(Table* table) void TableManager::ClearTablesThreadMemoryCache() { m_rwLock.RdLock(); - InternalTableMap::iterator it = m_tablesById.begin(); - while (it != m_tablesById.end()) { + ExternalTableMap::iterator it = m_tablesByExId.begin(); + while (it != m_tablesByExId.end()) { // lock table to prevent concurrent ddl and truncate operations it->second->RdLock(); it->second->ClearThreadMemoryCache(); it->second->Unlock(); - it++; + (void)++it; } m_rwLock.RdUnlock(); } @@ -68,7 +68,7 @@ void TableManager::ClearAllTables() while (it != m_tablesById.end()) { Table* table = it->second; delete table; - ++it; + (void)++it; } m_tablesById.clear(); diff --git a/src/gausskernel/storage/mot/core/system/common/table_manager.h b/src/gausskernel/storage/mot/core/system/common/table_manager.h index 0f90e6a39..d92789123 100644 --- a/src/gausskernel/storage/mot/core/system/common/table_manager.h +++ b/src/gausskernel/storage/mot/core/system/common/table_manager.h @@ -30,7 +30,7 @@ #include "utilities.h" #include "rw_lock.h" -#include +#include #include #include #include @@ -61,16 +61,16 @@ public: * @param sessionContext The session context for the operation. * @return Zero if the operation succeeded or an error code. */ - inline RC DropTable(Table* table, SessionContext* sessionContext) + inline RC DropTable(Table* table, TxnManager* txn) { if (table == nullptr) { return RC_ERROR; } MOT_LOG_INFO("Dropping table %s", table->GetLongTableName().c_str()); - RC status = DropTableInternal(table, sessionContext); + DropTableInternal(table, txn); delete table; - return status; + return RC_OK; } /** @@ -100,7 +100,7 @@ public: * @return The table object or null pointer if not found. * @note It is assumed that the envelope guards against concurrent removal of the table. */ - inline Table* GetTableSafeByExId(ExternalTableId tableId) + inline Table* GetTableSafeByExId(ExternalTableId tableId, bool wrLock = false) { Table* table = nullptr; m_rwLock.RdLock(); @@ -108,7 +108,11 @@ public: if (it != m_tablesByExId.end()) { table = it->second; if (table != nullptr) { - table->RdLock(); + if (wrLock) { + table->WrLock(); + } else { + table->RdLock(); + } } } m_rwLock.RdUnlock(); @@ -185,16 +189,15 @@ public: * @param[out] tablesQueue Receives all the tables. * @return The number of tables added. */ - inline uint32_t AddTablesToList(std::list& tablesQueue) + inline void AddTablesToList(std::list& tablesQueue) { m_rwLock.RdLock(); - for (InternalTableMap::iterator it = m_tablesById.begin(); it != m_tablesById.end(); ++it) { + for (ExternalTableMap::iterator it = m_tablesByExId.begin(); it != m_tablesByExId.end(); (void)++it) { tablesQueue.push_back(it->second); // lock the table, so it won't get deleted/truncated it->second->RdLock(); } m_rwLock.RdUnlock(); - return (uint32_t)tablesQueue.size(); } /** @brief Clears all object-pool table caches for the current thread. */ @@ -208,17 +211,17 @@ private: * @brief Helper method for table dropping. * @param table The table to delete. * @param sessionContext The session context for the operation. - * @return Zero if the operation succeeded or an error code. + * @return void. */ - inline RC DropTableInternal(Table* table, SessionContext* sessionContext) + inline void DropTableInternal(Table* table, TxnManager* txn) { m_rwLock.WrLock(); - m_tablesById.erase(table->GetTableId()); - m_tablesByExId.erase(table->GetTableExId()); - m_tablesByName.erase(table->GetLongTableName()); + (void)m_tablesById.erase(table->GetTableId()); + (void)m_tablesByExId.erase(table->GetTableExId()); + (void)m_tablesByName.erase(table->GetLongTableName()); m_rwLock.WrUnlock(); - sessionContext->GetTxnManager()->RemoveTableFromStat(table); - return table->DropImpl(); + txn->RemoveTableFromStat(table); + table->DropImpl(); } /** @typedef internal table map */ diff --git a/src/gausskernel/storage/mot/core/system/common/thread_id.cpp b/src/gausskernel/storage/mot/core/system/common/thread_id.cpp index b72b554a2..3eea01be5 100644 --- a/src/gausskernel/storage/mot/core/system/common/thread_id.cpp +++ b/src/gausskernel/storage/mot/core/system/common/thread_id.cpp @@ -31,7 +31,7 @@ #include "mot_atomic_ops.h" #include -#include +#include namespace MOT { DECLARE_LOGGER(ThreadIdPool, System) @@ -50,10 +50,10 @@ static MOTThreadId numaOrdinalThreadMap[MAX_THREAD_COUNT]; static enum ThreadIdInitPhase { INIT, LOCK_INIT, KEY_CREATE, ARRAY_ALLOC, DONE } initPhase = INIT; // the bit index is counted from MSB[0] to LSB[63]! -#define THREAD_ID_BIT_AT(bitIndex) (((uint64_t)1) << (THREADS_PER_WORD - (bitIndex)-1)) +#define THREAD_ID_BIT_AT(bitIndex) (((uint64_t)1) << ((THREADS_PER_WORD - (bitIndex)) - 1)) #define IS_THREAD_ID_BIT_SET(arrayIndex, bitIndex) (threadIdBitsetArray[arrayIndex] & THREAD_ID_BIT_AT(bitIndex)) -#define RAISE_THREAD_ID_BIT(arrayIndex, bitIndex) threadIdBitsetArray[arrayIndex] |= THREAD_ID_BIT_AT(bitIndex) -#define CLEAR_THREAD_ID_BIT(arrayIndex, bitIndex) threadIdBitsetArray[arrayIndex] &= ~THREAD_ID_BIT_AT(bitIndex) +#define RAISE_THREAD_ID_BIT(arrayIndex, bitIndex) (threadIdBitsetArray[arrayIndex] |= THREAD_ID_BIT_AT(bitIndex)) +#define CLEAR_THREAD_ID_BIT(arrayIndex, bitIndex) (threadIdBitsetArray[arrayIndex] &= ~THREAD_ID_BIT_AT(bitIndex)) // pay attention: the bit set is inverted - "0" means occupied and "1" means free for usage #define MARK_THREAD_ID_USED(arrayIndex, bitIndex) CLEAR_THREAD_ID_BIT(arrayIndex, bitIndex) @@ -96,7 +96,7 @@ static void ScheduleThreadIdCleanup() // install auto-cleanup for current thread in case a call to FreeThreadId() is missing // pay attention we add 1 to thread id because pthread key destructor is called only if key for // the current thread is non-zero, and we want to recycle also thread id zero - pthread_setspecific(threadIdCleanupKey, (const void*)(uint64_t)(MOTCurrThreadId + 1)); + (void)pthread_setspecific(threadIdCleanupKey, (const void*)(uint64_t)(MOTCurrThreadId + 1)); } static void SetCurrentThreadId(MOTThreadId threadId) @@ -185,11 +185,11 @@ extern void DestroyThreadIdPool() // fall through case KEY_CREATE: - pthread_key_delete(threadIdCleanupKey); + (void)pthread_key_delete(threadIdCleanupKey); // fall through case LOCK_INIT: - pthread_spin_destroy(&threadIdLock); + (void)pthread_spin_destroy(&threadIdLock); // fall through default: @@ -204,7 +204,7 @@ extern MOTThreadId AllocThreadId() if (MOTCurrThreadId != INVALID_THREAD_ID) { MOT_LOG_TRACE("Double attempt to allocate thread identifier for thread %" PRIu16 " declined", MOTCurrThreadId); } else { - pthread_spin_lock(&threadIdLock); + (void)pthread_spin_lock(&threadIdLock); for (uint16_t arrayIndex = 0; arrayIndex < threadIdArraySize; ++arrayIndex) { if (threadIdBitsetArray[arrayIndex] != 0) { uint16_t bitIndex = __builtin_clzll(threadIdBitsetArray[arrayIndex]); @@ -215,7 +215,7 @@ extern MOTThreadId AllocThreadId() break; } } - pthread_spin_unlock(&threadIdLock); + (void)pthread_spin_unlock(&threadIdLock); if (MOTCurrThreadId == INVALID_THREAD_ID) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -235,11 +235,11 @@ extern MOTThreadId AllocThreadIdHighest() if (MOTCurrThreadId != INVALID_THREAD_ID) { MOT_LOG_TRACE("Double attempt to allocate thread identifier for thread %" PRIu16 " declined", MOTCurrThreadId); } else { - pthread_spin_lock(&threadIdLock); + (void)pthread_spin_lock(&threadIdLock); // since computation is tedious in case maxThreadCount is not a full multiple of 64, we do it in the opposite // way for (uint16_t i = 0; i < maxThreadCount; ++i) { - MOTThreadId threadId = maxThreadCount - i - 1; + MOTThreadId threadId = (maxThreadCount - i) - 1; uint16_t arrayIndex = threadId / THREADS_PER_WORD; uint16_t bitIndex = threadId % THREADS_PER_WORD; if (IS_THREAD_ID_FREE(arrayIndex, bitIndex)) { @@ -247,7 +247,7 @@ extern MOTThreadId AllocThreadIdHighest() break; } } - pthread_spin_unlock(&threadIdLock); + (void)pthread_spin_unlock(&threadIdLock); if (MOTCurrThreadId == INVALID_THREAD_ID) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -268,11 +268,11 @@ extern MOTThreadId AllocThreadIdNumaHighest(int nodeId) MOT_LOG_TRACE("Double attempt to allocate thread identifier for thread %" PRIu16 " declined", MOTCurrThreadId); } else { Affinity& affinity = GetTaskAffinity(); - pthread_spin_lock(&threadIdLock); + (void)pthread_spin_lock(&threadIdLock); // since computation is tedious in case maxThreadCount is not a full multiple of 64, we do it in the opposite // way for (uint16_t i = 0; i < maxThreadCount; ++i) { - MOTThreadId threadId = maxThreadCount - i - 1; + MOTThreadId threadId = (maxThreadCount - i) - 1; uint16_t arrayIndex = threadId / THREADS_PER_WORD; uint16_t bitIndex = threadId % THREADS_PER_WORD; if (IS_THREAD_ID_FREE(arrayIndex, bitIndex)) { @@ -284,7 +284,7 @@ extern MOTThreadId AllocThreadIdNumaHighest(int nodeId) } } } - pthread_spin_unlock(&threadIdLock); + (void)pthread_spin_unlock(&threadIdLock); if (MOTCurrThreadId == INVALID_THREAD_ID) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -314,7 +314,7 @@ extern MOTThreadId ReserveThreadId() { MOTThreadId result = INVALID_THREAD_ID; - pthread_spin_lock(&threadIdLock); + (void)pthread_spin_lock(&threadIdLock); for (uint16_t arrayIndex = 0; arrayIndex < threadIdArraySize; ++arrayIndex) { if (threadIdBitsetArray[arrayIndex] != 0) { uint16_t bitIndex = __builtin_clzll(threadIdBitsetArray[arrayIndex]); @@ -327,7 +327,7 @@ extern MOTThreadId ReserveThreadId() break; } } - pthread_spin_unlock(&threadIdLock); + (void)pthread_spin_unlock(&threadIdLock); return result; } @@ -351,7 +351,7 @@ extern void FreeThreadId() MOT_ATOMIC_DEC(currentThreadCount); // cancel auto-cleanup for current thread (reset pthread key to zero) - pthread_setspecific(threadIdCleanupKey, 0); + (void)pthread_setspecific(threadIdCleanupKey, nullptr); MOT_LOG_TRACE("De-allocated internal thread id %" PRIu16, threadId); } } @@ -385,10 +385,10 @@ static void FreeThreadIdInternal(MOTThreadId threadId) uint16_t arrayIndex = threadId / THREADS_PER_WORD; uint16_t bitIndex = threadId % THREADS_PER_WORD; - pthread_spin_lock(&threadIdLock); + (void)pthread_spin_lock(&threadIdLock); MOT_ASSERT(IS_THREAD_ID_USED(arrayIndex, bitIndex)); MARK_THREAD_ID_FREE(arrayIndex, bitIndex); - pthread_spin_unlock(&threadIdLock); + (void)pthread_spin_unlock(&threadIdLock); } static int InitNumaOrdinalThreadMap() @@ -397,11 +397,11 @@ static int InitNumaOrdinalThreadMap() // this we we are ready for any future thread distribution over NUMA sockets. int result = 0; - uint32_t nodeCount = GetGlobalConfiguration().m_numaNodes; + int nodeCount = GetGlobalConfiguration().m_numaNodes; if (nodeCount > MEM_MAX_NUMA_NODES) { MOT_REPORT_PANIC(MOT_ERROR_INVALID_CFG, "Thread Id Pool Initialization", - "Invalid configuration for number of NUMA node %u (exceeds allowed maximum %u)", + "Invalid configuration for number of NUMA node %d (exceeds allowed maximum %u)", nodeCount, (unsigned)MEM_MAX_NUMA_NODES); result = MOT_ERROR_INVALID_CFG; @@ -412,7 +412,7 @@ static int InitNumaOrdinalThreadMap() Affinity& affinity = GetTaskAffinity(); // this is always valid, even on thread-pool environment for (MOTThreadId i = 0; i < maxThreadCount; ++i) { int nodeId = affinity.GetAffineNuma(i); - if (nodeId < (int)nodeCount) { + if (nodeId < nodeCount) { // record the number of thread affined to this socket, and the ordinal number of current thread in this // socket numaOrdinalThreadMap[i] = counterMap[nodeId]++; diff --git a/src/gausskernel/storage/mot/core/system/common/thread_id.h b/src/gausskernel/storage/mot/core/system/common/thread_id.h index 826c261b5..6448e0546 100644 --- a/src/gausskernel/storage/mot/core/system/common/thread_id.h +++ b/src/gausskernel/storage/mot/core/system/common/thread_id.h @@ -25,7 +25,7 @@ #ifndef THREAD_ID_H #define THREAD_ID_H -#include +#include #include "libintl.h" #include "postgres.h" #include "knl/knl_thread.h" diff --git a/src/gausskernel/storage/mot/core/system/global.cpp b/src/gausskernel/storage/mot/core/system/global.cpp index 98cd9be6f..701bae886 100644 --- a/src/gausskernel/storage/mot/core/system/global.cpp +++ b/src/gausskernel/storage/mot/core/system/global.cpp @@ -59,17 +59,32 @@ extern const char* RcToString(RC rc) case RC_LOCAL_ROW_DELETED: return "Local row deleted"; case RC_INSERT_ON_EXIST: - return "Insert on exist"; // what does that mean? + return "Insert on exist"; case RC_INDEX_RETRY_INSERT: return "Insert to index failed: row concurrency"; case RC_INDEX_DELETE: return "Insert to index failed: row deleted"; + case RC_GC_INFO_REMOVE: + return "GC info can be removed"; + case RC_SERIALIZATION_FAILURE: + return "could not serialize access due to concurrent update"; case RC_LOCAL_ROW_NOT_VISIBLE: return "Local row not visible"; case RC_MEMORY_ALLOCATION_ERROR: return "Out of memory"; case RC_ILLEGAL_ROW_STATE: return "Illegal row state"; + case RC_NULL_VIOLATION: + return "Null-constraint violation"; + case RC_JIT_SP_EXCEPTION: + return "JIT stored procedure exception thrown"; + case RC_TXN_ABORTED: + return "Current transaction is aborted"; + case RC_CONCURRENT_MODIFICATION: + return "Concurrent modification"; + case RC_STATEMENT_CANCELED: + return "Statement canceled due to user request"; + default: return "N/A"; } diff --git a/src/gausskernel/storage/mot/core/system/global.h b/src/gausskernel/storage/mot/core/system/global.h index 52ee01773..a7b523a42 100644 --- a/src/gausskernel/storage/mot/core/system/global.h +++ b/src/gausskernel/storage/mot/core/system/global.h @@ -26,12 +26,12 @@ #define MOT_GLOBAL_H #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include +#include #include #include #include @@ -94,14 +94,20 @@ enum RC : uint32_t { RC_LOCAL_ROW_FOUND, RC_LOCAL_ROW_NOT_FOUND, RC_LOCAL_ROW_DELETED, + RC_PRIMARY_SENTINEL_NOT_MAPPED, RC_INSERT_ON_EXIST, RC_INDEX_RETRY_INSERT, RC_INDEX_DELETE, + RC_GC_INFO_REMOVE, RC_LOCAL_ROW_NOT_VISIBLE, RC_MEMORY_ALLOCATION_ERROR, RC_ILLEGAL_ROW_STATE, RC_NULL_VIOLATION, RC_PANIC, + RC_JIT_SP_EXCEPTION, + RC_TXN_ABORTED, + RC_CONCURRENT_MODIFICATION, + RC_STATEMENT_CANCELED, /** @var operation currently n.a. */ RC_NA, @@ -116,6 +122,8 @@ enum RC : uint32_t { */ extern const char* RcToString(RC rc); +enum SubTxnOperType : uint32_t { SUB_TXN_READ = 0, SUB_TXN_DML = 1, SUB_TXN_DDL = 2 }; + /** * @enum Transaction State. * reflects the envelope state @@ -205,7 +213,7 @@ enum CheckpointPhase : uint8_t { */ enum AccessType : uint8_t { /** @var Invalid row access code. */ - INV, + INV = 0, /** @var Denotes read row access code. */ RD, @@ -231,14 +239,24 @@ enum AccessType : uint8_t { INC, DEC - }; -#define BITMAP_BYTE_IX(x) ((x) >> 3) +enum UpdateIndexColumnType : uint8_t { + /** @var Checkpoint phase is unknown. */ + UPDATE_COLUMN_NONE, + + /** @var Update primary index column */ + UPDATE_COLUMN_PRIMARY, + + /** @var Update secondary index column. */ + UPDATE_COLUMN_SECONDARY +}; + +#define BITMAP_BYTE_IX(x) (static_cast(x) >> 3) #define BITMAP_GETLEN(x) (BITMAP_BYTE_IX(x) + 1) -#define BITMAP_SET(b, x) (b[BITMAP_BYTE_IX(x)] |= (1 << ((x) & 0x07))) -#define BITMAP_CLEAR(b, x) (b[BITMAP_BYTE_IX(x)] &= ~(1 << ((x) & 0x07))) -#define BITMAP_GET(b, x) (b[BITMAP_BYTE_IX(x)] & (1 << ((x) & 0x07))) +#define BITMAP_SET(b, x) ((b)[BITMAP_BYTE_IX(x)] |= (1U << ((x) & 0x07))) +#define BITMAP_CLEAR(b, x) ((b)[BITMAP_BYTE_IX(x)] &= ~(1U << ((x) & 0x07))) +#define BITMAP_GET(b, x) ((b)[BITMAP_BYTE_IX(x)] & (1U << ((x) & 0x07))) /************************************************/ // constants @@ -252,6 +270,9 @@ enum AccessType : uint8_t { #define INVALID_TRANSACTION_ID ((TransactionId)0) // Equal to InvalidTransactionId in the envelope. +#define INVALID_CSN ((uint64_t)0) // Equal to InvalidCommitSeqNo in the envelope. +#define INITIAL_CSN ((uint64_t)3) // Equal to COMMITSEQNO_FIRST_NORMAL in the envelope. + // masstree cleanup #define MASSTREE_OBJECT_COUNT_PER_CLEANUP 8000 // max # of objects to free per rcu_quiesce() call #define MASSTREE_CLEANUP_THRESHOLD 10000 // max # of object to reclaim before calling reclaiming memory @@ -282,24 +303,29 @@ inline void Prefetch(const void* ptr) #endif } -// Isolation Levels -#define SERIALIZABLE 4 -#define SNAPSHOT 3 -#define REPEATABLE_READ 2 -#define READ_COMMITED 1 +// Isolation Levels - // Equal to Xact isolation levels in the envelope. +enum ISOLATION_LEVEL : uint32_t { + READ_COMMITED = 1, + + REPEATABLE_READ, + + SERIALIZABLE +}; /* Storage Params */ #define MAX_NUM_INDEXES (10U) #define MAX_KEY_COLUMNS (10U) #define MAX_TUPLE_SIZE 16384 // in bytes -#define MAX_VARCHAR_LEN 1024 +#define MAX_VARCHAR_LEN 2052 // Do not change this. Masstree assumes 15 for optimization purposes #define BTREE_ORDER 15 /** @define Constant denoting indentation used for MOT printouts. */ #define PRINT_REPORT_INDENT 2 -} // namespace MOT +/* GC Parmas */ +#define GC_MAX_ELEMENTS_TO_SKIP 5 +} // namespace MOT #endif // MOT_GLOBAL_H diff --git a/src/gausskernel/storage/mot/core/system/mot_configuration.cpp b/src/gausskernel/storage/mot/core/system/mot_configuration.cpp index 2c5fea7bd..1f55b2638 100644 --- a/src/gausskernel/storage/mot/core/system/mot_configuration.cpp +++ b/src/gausskernel/storage/mot/core/system/mot_configuration.cpp @@ -47,9 +47,6 @@ constexpr uint64_t MOTConfiguration::SCALE_SECONDS; constexpr bool MOTConfiguration::DEFAULT_ENABLE_REDO_LOG; constexpr LoggerType MOTConfiguration::DEFAULT_LOGGER_TYPE; constexpr RedoLogHandlerType MOTConfiguration::DEFAULT_REDO_LOG_HANDLER_TYPE; -constexpr uint32_t MOTConfiguration::DEFAULT_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT; -constexpr uint32_t MOTConfiguration::MIN_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT; -constexpr uint32_t MOTConfiguration::MAX_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT; constexpr bool MOTConfiguration::DEFAULT_ENABLE_GROUP_COMMIT; constexpr uint64_t MOTConfiguration::DEFAULT_GROUP_COMMIT_SIZE; constexpr uint64_t MOTConfiguration::MIN_GROUP_COMMIT_SIZE; @@ -74,6 +71,13 @@ constexpr uint32_t MOTConfiguration::DEFAULT_CHECKPOINT_RECOVERY_WORKERS; constexpr uint32_t MOTConfiguration::MIN_CHECKPOINT_RECOVERY_WORKERS; constexpr uint32_t MOTConfiguration::MAX_CHECKPOINT_RECOVERY_WORKERS; constexpr bool MOTConfiguration::DEFAULT_ENABLE_LOG_RECOVERY_STATS; +constexpr RecoveryMode MOTConfiguration::DEFAULT_RECOVERY_MODE; +constexpr uint32_t MOTConfiguration::DEFAULT_PARALLEL_RECOVERY_WORKERS; +constexpr uint32_t MOTConfiguration::MIN_PARALLEL_RECOVERY_WORKERS; +constexpr uint32_t MOTConfiguration::MAX_PARALLEL_RECOVERY_WORKERS; +constexpr uint32_t MOTConfiguration::DEFAULT_PARALLEL_RECOVERY_QUEUE_SIZE; +constexpr uint32_t MOTConfiguration::MIN_PARALLEL_RECOVERY_QUEUE_SIZE; +constexpr uint32_t MOTConfiguration::MAX_PARALLEL_RECOVERY_QUEUE_SIZE; // machine configuration members constexpr uint16_t MOTConfiguration::DEFAULT_NUMA_NODES; constexpr uint16_t MOTConfiguration::DEFAULT_CORES_PER_CPU; @@ -169,11 +173,16 @@ constexpr uint64_t MOTConfiguration::MIN_GC_HIGH_RECLAIM_THRESHOLD_BYTES; constexpr uint64_t MOTConfiguration::MAX_GC_HIGH_RECLAIM_THRESHOLD_BYTES; // JIT configuration members constexpr bool MOTConfiguration::DEFAULT_ENABLE_MOT_CODEGEN; -constexpr bool MOTConfiguration::DEFAULT_FORCE_MOT_PSEUDO_CODEGEN; +constexpr bool MOTConfiguration::DEFAULT_ENABLE_MOT_QUERY_CODEGEN; +constexpr bool MOTConfiguration::DEFAULT_ENABLE_MOT_SP_CODEGEN; +constexpr const char* MOTConfiguration::DEFAULT_MOT_SP_CODEGEN_ALLOWED; +constexpr const char* MOTConfiguration::DEFAULT_MOT_SP_CODEGEN_PROHIBITED; +constexpr const char* MOTConfiguration::DEFAULT_MOT_PURE_SP_CODEGEN; constexpr bool MOTConfiguration::DEFAULT_ENABLE_MOT_CODEGEN_PRINT; constexpr uint32_t MOTConfiguration::DEFAULT_MOT_CODEGEN_LIMIT; constexpr uint32_t MOTConfiguration::MIN_MOT_CODEGEN_LIMIT; constexpr uint32_t MOTConfiguration::MAX_MOT_CODEGEN_LIMIT; +constexpr bool MOTConfiguration::DEFAULT_ENABLE_MOT_CODEGEN_PROFILE; // storage configuration constexpr bool MOTConfiguration::DEFAULT_ALLOW_INDEX_ON_NULLABLE_COLUMN; constexpr IndexTreeFlavor MOTConfiguration::DEFAULT_INDEX_TREE_FLAVOR; @@ -185,7 +194,8 @@ constexpr uint64_t MOTConfiguration::MAX_CFG_MONITOR_PERIOD_SECONDS; constexpr bool MOTConfiguration::DEFAULT_RUN_INTERNAL_CONSISTENCY_VALIDATION; constexpr uint64_t MOTConfiguration::DEFAULT_TOTAL_MEMORY_MB; -static constexpr unsigned int MAX_NUMA_NODES = 16u; +static constexpr int MAX_NUMA_NODES = 16; + #define IS_HYPER_THREAD_CMD "lscpu | grep \"Thread(s) per core:\" | awk '{print $4}'" static bool ParseLoggerType( @@ -256,6 +266,19 @@ static bool ParseRedoLogHandlerType(const std::string& cfgName, const std::strin return result; } +static bool ParseRecoveryMode(const std::string& cfgName, const std::string& variableName, const std::string& newValue, + RecoveryMode* variableValue) +{ + bool result = (cfgName == variableName); + if (result) { + *variableValue = RecoveryModeFromString(newValue.c_str()); + if (*variableValue == RecoveryMode::RECOVERY_INVALID) { + result = false; + } + } + return result; +} + static bool ParseBool( const std::string& cfgName, const std::string& variableName, const std::string& newValue, bool* variableValue) { @@ -305,7 +328,7 @@ static bool ParseUint16( { bool result = (cfgName == variableName); if (result) { - *variableValue = (uint16_t)std::stoul(newValue); + *variableValue = static_cast(std::stoul(newValue)); MOT_ASSERT(!newValue.empty()); } return result; @@ -365,8 +388,8 @@ bool MOTConfiguration::FindNumaNodes(int* maxNodes) return false; } - if ((unsigned int)*maxNodes >= MAX_NUMA_NODES) { - MOT_LOG_ERROR("sys_numa_num_configured_nodes() => %d, while MAX_NUMA_NODES=%u - cannot proceed", + if (*maxNodes >= MAX_NUMA_NODES) { + MOT_LOG_ERROR("sys_numa_num_configured_nodes() => %d, while MAX_NUMA_NODES=%d - cannot proceed", *maxNodes, MAX_NUMA_NODES); return false; @@ -392,20 +415,20 @@ bool MOTConfiguration::FindNumProcessors(uint16_t* maxCoresPerNode, CpuNodeMap* MOT_LOG_ERROR("Invalid NUMA configuration numa_node_of_cpu(%d) => %d", i, node); return false; } - if ((unsigned int)node >= MAX_NUMA_NODES) { + if (node >= MAX_NUMA_NODES) { MOT_LOG_ERROR( - "CPU %d is located in node %d, while MAX_NUMA_NODES=%u - cannot proceed", i, node, MAX_NUMA_NODES); + "CPU %d is located in node %d, while MAX_NUMA_NODES=%d - cannot proceed", i, node, MAX_NUMA_NODES); return false; } cpusPerNode[node]++; (*cpuNodeMapper)[i] = node; - (*cpuOsMapper)[node].insert(i); + (void)(*cpuOsMapper)[node].insert(i); } // dynamically calculate number of CPU cores per NUMA node instead of hard-coded config value if (maxCoresPerNode != nullptr) { *maxCoresPerNode = 0; - for (unsigned int j = 0; j < MAX_NUMA_NODES; j++) { + for (int j = 0; j < MAX_NUMA_NODES; j++) { if (cpusPerNode[j] > *maxCoresPerNode) { *maxCoresPerNode = cpusPerNode[j]; } @@ -415,21 +438,21 @@ bool MOTConfiguration::FindNumProcessors(uint16_t* maxCoresPerNode, CpuNodeMap* return true; } -void MOTConfiguration::SetMaskToAllCoresinNumaSocket(cpu_set_t& mask, uint64_t threadId) +void MOTConfiguration::SetMaskToAllCoresinNumaSocketByCoreId(cpu_set_t& mask, int coreId) { - int nodeId = GetCpuNode(threadId); + int nodeId = GetCpuNode(coreId); auto nodeMap = m_osCpuMap[nodeId]; - for (auto it = nodeMap.begin(); it != nodeMap.end(); ++it) { + for (auto it = nodeMap.begin(); it != nodeMap.end(); (void)++it) { if (MotSysNumaCpuAllowed(*it)) { CPU_SET(*it, &mask); } } } -void MOTConfiguration::SetMaskToAllCoresinNumaSocket2(cpu_set_t& mask, int nodeId) +void MOTConfiguration::SetMaskToAllCoresinNumaSocketByNodeId(cpu_set_t& mask, int nodeId) { auto nodeMap = m_osCpuMap[nodeId]; - for (auto it = nodeMap.begin(); it != nodeMap.end(); ++it) { + for (auto it = nodeMap.begin(); it != nodeMap.end(); (void)++it) { if (MotSysNumaCpuAllowed(*it)) { CPU_SET(*it, &mask); } @@ -440,7 +463,6 @@ MOTConfiguration::MOTConfiguration() : m_enableRedoLog(DEFAULT_ENABLE_REDO_LOG), m_loggerType(DEFAULT_LOGGER_TYPE), m_redoLogHandlerType(DEFAULT_REDO_LOG_HANDLER_TYPE), - m_asyncRedoLogBufferArrayCount(DEFAULT_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT), m_enableGroupCommit(DEFAULT_ENABLE_GROUP_COMMIT), m_groupCommitSize(DEFAULT_GROUP_COMMIT_SIZE), m_groupCommitTimeoutUSec(DEFAULT_GROUP_COMMIT_TIMEOUT_USEC), @@ -449,6 +471,9 @@ MOTConfiguration::MOTConfiguration() m_checkpointDir(DEFAULT_CHECKPOINT_DIR), m_checkpointSegThreshold(DEFAULT_CHECKPOINT_SEGSIZE_BYTES), m_checkpointWorkers(DEFAULT_CHECKPOINT_WORKERS), + m_recoveryMode(DEFAULT_RECOVERY_MODE), + m_parallelRecoveryWorkers(DEFAULT_PARALLEL_RECOVERY_WORKERS), + m_parallelRecoveryQueueSize(DEFAULT_PARALLEL_RECOVERY_QUEUE_SIZE), m_checkpointRecoveryWorkers(DEFAULT_CHECKPOINT_RECOVERY_WORKERS), m_abortBufferEnable(true), m_preAbort(true), @@ -492,18 +517,23 @@ MOTConfiguration::MOTConfiguration() m_sessionLargeBufferStoreSizeMB(DEFAULT_SESSION_LARGE_BUFFER_STORE_SIZE_MB), m_sessionLargeBufferStoreMaxObjectSizeMB(DEFAULT_SESSION_LARGE_BUFFER_STORE_MAX_OBJECT_SIZE_MB), m_sessionMaxHugeObjectSizeMB(DEFAULT_SESSION_MAX_HUGE_OBJECT_SIZE_MB), - m_gcEnable(DEFAULT_GC_ENABLE), m_gcReclaimThresholdBytes(DEFAULT_GC_RECLAIM_THRESHOLD_BYTES), m_gcReclaimBatchSize(DEFAULT_GC_RECLAIM_BATCH_SIZE), m_gcHighReclaimThresholdBytes(DEFAULT_GC_HIGH_RECLAIM_THRESHOLD_BYTES), m_enableCodegen(DEFAULT_ENABLE_MOT_CODEGEN), - m_forcePseudoCodegen(DEFAULT_FORCE_MOT_PSEUDO_CODEGEN), + m_enableQueryCodegen(DEFAULT_ENABLE_MOT_QUERY_CODEGEN), + m_enableSPCodegen(DEFAULT_ENABLE_MOT_SP_CODEGEN), + m_spCodegenAllowed(DEFAULT_MOT_SP_CODEGEN_ALLOWED), + m_spCodegenProhibited(DEFAULT_MOT_SP_CODEGEN_PROHIBITED), + m_pureSPCodegen(DEFAULT_MOT_PURE_SP_CODEGEN), m_enableCodegenPrint(DEFAULT_ENABLE_MOT_CODEGEN_PRINT), m_codegenLimit(DEFAULT_MOT_CODEGEN_LIMIT), + m_enableCodegenProfile(DEFAULT_ENABLE_MOT_CODEGEN_PROFILE), m_allowIndexOnNullableColumn(DEFAULT_ALLOW_INDEX_ON_NULLABLE_COLUMN), m_indexTreeFlavor(DEFAULT_INDEX_TREE_FLAVOR), m_configMonitorPeriodSeconds(DEFAULT_CFG_MONITOR_PERIOD_SECONDS), m_runInternalConsistencyValidation(DEFAULT_RUN_INTERNAL_CONSISTENCY_VALIDATION), + m_runInternalMvccConsistencyValidation(DEFAULT_RUN_INTERNAL_CONSISTENCY_VALIDATION), m_totalMemoryMb(DEFAULT_TOTAL_MEMORY_MB), m_suppressLog(0), m_loadExtraParams(false) @@ -516,7 +546,7 @@ void MOTConfiguration::Initialize() MotSysNumaInit(); int numa = DEFAULT_NUMA_NODES; if (FindNumaNodes(&numa)) { - m_numaNodes = (uint16_t)numa; + m_numaNodes = static_cast(numa); } else { MOT_LOG_WARN("Failed to infer the number of NUMA nodes on current machine, defaulting to %d", numa); m_numaAvailable = false; @@ -526,7 +556,7 @@ void MOTConfiguration::Initialize() if (FindNumProcessors(&cores, &m_cpuNodeMapper, &m_osCpuMap)) { m_coresPerCpu = cores; } else { - MOT_LOG_WARN("Failed to infer the number of cores on the current machine, defaulting to %u", (unsigned)cores); + MOT_LOG_WARN("Failed to infer the number of cores on the current machine, defaulting to %" PRIu16, cores); m_numaAvailable = false; } @@ -546,7 +576,6 @@ bool MOTConfiguration::SetFlag(const std::string& name, const std::string& value if (ParseBool(name, "enable_redo_log", value, &m_enableRedoLog)) { } else if (ParseLoggerType(name, "logger_type", value, &m_loggerType)) { } else if (ParseRedoLogHandlerType(name, "redo_log_handler_type", value, &m_redoLogHandlerType)) { - } else if (ParseUint32(name, "async_log_buffer_count", value, &m_asyncRedoLogBufferArrayCount)) { } else if (ParseBool(name, "enable_group_commit", value, &m_enableGroupCommit)) { } else if (ParseUint64(name, "group_commit_size", value, &m_groupCommitSize)) { } else if (ParseUint64(name, "group_commit_timeout_usec", value, &m_groupCommitTimeoutUSec)) { @@ -556,6 +585,9 @@ bool MOTConfiguration::SetFlag(const std::string& name, const std::string& value } else if (ParseUint64(name, "checkpoint_segsize", value, &m_checkpointSegThreshold)) { } else if (ParseUint32(name, "checkpoint_workers", value, &m_checkpointWorkers)) { } else if (ParseUint32(name, "checkpoint_recovery_workers", value, &m_checkpointRecoveryWorkers)) { + } else if (ParseRecoveryMode(name, "recovery_mode", value, &m_recoveryMode)) { + } else if (ParseUint32(name, "parallel_recovery_workers", value, &m_parallelRecoveryWorkers)) { + } else if (ParseUint32(name, "parallel_recovery_queue_size", value, &m_parallelRecoveryQueueSize)) { } else if (ParseBool(name, "abort_buffer_enable", value, &m_abortBufferEnable)) { } else if (ParseBool(name, "pre_abort", value, &m_preAbort)) { } else if (ParseValidation(name, "validation_lock", value, &m_validationLock)) { @@ -598,9 +630,11 @@ bool MOTConfiguration::SetFlag(const std::string& name, const std::string& value &m_sessionLargeBufferStoreMaxObjectSizeMB)) { } else if (ParseUint64(name, "session_max_huge_object_size_mb", value, &m_sessionMaxHugeObjectSizeMB)) { } else if (ParseBool(name, "enable_mot_codegen", value, &m_enableCodegen)) { - } else if (ParseBool(name, "force_mot_pseudo_codegen", value, &m_forcePseudoCodegen)) { + } else if (ParseBool(name, "enable_mot_query_codegen", value, &m_enableQueryCodegen)) { + } else if (ParseBool(name, "enable_mot_sp_codegen", value, &m_enableSPCodegen)) { } else if (ParseBool(name, "enable_mot_codegen_print", value, &m_enableCodegenPrint)) { } else if (ParseUint32(name, "mot_codegen_limit", value, &m_codegenLimit)) { + } else if (ParseBool(name, "enable_mot_codegen_profile", value, &m_enableCodegenProfile)) { } else if (ParseBool(name, "allow_index_on_nullable_column", value, &m_allowIndexOnNullableColumn)) { } else if (ParseIndexTreeFlavor(name, "index_tree_flavor", value, &m_indexTreeFlavor)) { } else if (ParseUint64(name, "config_monitor_period_seconds", value, &m_configMonitorPeriodSeconds)) { @@ -625,18 +659,18 @@ int MOTConfiguration::GetCpuNode(int cpu) const return (itr != m_cpuNodeMapper.end()) ? itr->second : -1; } -uint16_t MOTConfiguration::GetCoreByConnidFP(uint16_t cpu) const +int MOTConfiguration::GetCoreByConnidFP(int cpu) const { - uint16_t numOfRealCores = (IsHyperThread() == true) ? m_coresPerCpu / 2 : m_coresPerCpu; // 2 is for HyperThread + int numOfRealCores = (IsHyperThread()) ? m_coresPerCpu / 2 : m_coresPerCpu; // 2 is for HyperThread // Lets pin physical first - // cpu is already modulu of(numaNodes*num_cores) - if (cpu < m_numaNodes * numOfRealCores) { - cpu = cpu % (m_numaNodes * numOfRealCores); - uint16_t coreIndex = cpu % numOfRealCores; - uint16_t numaId = cpu / numOfRealCores; + // cpu is already modulo of (numaNodes * num_cores) + if (cpu < ((int)m_numaNodes * numOfRealCores)) { + cpu = cpu % ((int)m_numaNodes * numOfRealCores); + int coreIndex = cpu % numOfRealCores; + int numaId = cpu / numOfRealCores; int counter = 0; auto coreSet = m_osCpuMap.find(numaId); - for (auto it = coreSet->second.begin(); it != coreSet->second.end(); ++it) { + for (auto it = coreSet->second.begin(); it != coreSet->second.end(); (void)++it) { if (counter == coreIndex) { return *it; } @@ -651,10 +685,9 @@ uint16_t MOTConfiguration::GetCoreByConnidFP(uint16_t cpu) const int MOTConfiguration::GetMappedCore(int logicId) const { - int counter = 0; - for (auto it = m_osCpuMap.begin(); it != m_osCpuMap.end(); ++it) { - for (auto it2 = (*it).second.begin(); it2 != (*it).second.end(); ++it2) { + for (auto it = m_osCpuMap.begin(); it != m_osCpuMap.end(); (void)++it) { + for (auto it2 = (*it).second.begin(); it2 != (*it).second.end(); (void)++it2) { if (counter == logicId) { return *it2; } @@ -664,6 +697,22 @@ int MOTConfiguration::GetMappedCore(int logicId) const return -1; } +int MOTConfiguration::GetCoreFromNumaNodeByIndex(int numaId, int logicId) const +{ + auto coreSet = m_osCpuMap.find(numaId); + if (coreSet != m_osCpuMap.end()) { + int counter = 0; + for (auto it = coreSet->second.begin(); it != coreSet->second.end(); (void)++it) { + if (counter == logicId) { + return *it; + } + counter++; + } + } + + return INVALID_CPU_ID; +} + #define UPDATE_BOOL_CFG(var, cfgPath, defaultValue) \ UpdateBoolConfigItem(var, cfg->GetConfigValue(cfgPath, defaultValue, m_suppressLog == 0), cfgPath) @@ -887,6 +936,9 @@ void MOTConfiguration::LoadConfig() MOT_LOG_TRACE("Loading main configuration"); const LayeredConfigTree* cfg = ConfigManager::GetInstance().GetLayeredConfigTree(); + // load component log levels so we can enable traces in this logger + UpdateComponentLogLevel(); + // logger configuration if (m_loadExtraParams) { UPDATE_BOOL_CFG(m_enableRedoLog, "enable_redo_log", DEFAULT_ENABLE_REDO_LOG); @@ -897,11 +949,6 @@ void MOTConfiguration::LoadConfig() // overridden by the external configuration loader GaussdbConfigLoader (so in effect whatever is defined in // mot.conf is discarded). See GaussdbConfigLoader::ConfigureRedoLogHandler() for more details. UPDATE_USER_CFG(m_redoLogHandlerType, "redo_log_handler_type", DEFAULT_REDO_LOG_HANDLER_TYPE); - UPDATE_INT_CFG(m_asyncRedoLogBufferArrayCount, - "async_log_buffer_count", - DEFAULT_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT, - MIN_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT, - MAX_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT); // commit configuration UPDATE_BOOL_CFG(m_enableGroupCommit, "enable_group_commit", DEFAULT_ENABLE_GROUP_COMMIT); @@ -922,7 +969,7 @@ void MOTConfiguration::LoadConfig() UPDATE_BOOL_CFG(m_enableCheckpoint, "enable_checkpoint", DEFAULT_ENABLE_CHECKPOINT); } - if (!m_enableCheckpoint && m_enableRedoLog) { + if (!m_enableCheckpoint && !m_enableRedoLog) { if (m_suppressLog == 0) { MOT_LOG_WARN("Disabling redo_log forcibly as the checkpoint is disabled"); } @@ -949,6 +996,34 @@ void MOTConfiguration::LoadConfig() MIN_CHECKPOINT_RECOVERY_WORKERS, MAX_CHECKPOINT_RECOVERY_WORKERS); + if (m_loadExtraParams) { + UPDATE_USER_CFG(m_recoveryMode, "recovery_mode", DEFAULT_RECOVERY_MODE); + } + + UPDATE_INT_CFG(m_parallelRecoveryWorkers, + "parallel_recovery_workers", + DEFAULT_PARALLEL_RECOVERY_WORKERS, + MIN_PARALLEL_RECOVERY_WORKERS, + MAX_PARALLEL_RECOVERY_WORKERS); + + UPDATE_INT_CFG(m_parallelRecoveryQueueSize, + "parallel_recovery_queue_size", + DEFAULT_PARALLEL_RECOVERY_QUEUE_SIZE, + MIN_PARALLEL_RECOVERY_QUEUE_SIZE, + MAX_PARALLEL_RECOVERY_QUEUE_SIZE); + + if (m_parallelRecoveryQueueSize < m_parallelRecoveryWorkers) { + if (m_suppressLog == 0) { + MOT_LOG_WARN("Invalid recovery configuration: parallel_recovery_queue_size (%" PRIu32 ") is lesser than " + "parallel_recovery_workers (%" PRIu32 "), changing parallel_recovery_queue_size to (%" PRIu32 + ")", + m_parallelRecoveryQueueSize, + m_parallelRecoveryWorkers, + m_parallelRecoveryWorkers); + } + m_parallelRecoveryQueueSize = m_parallelRecoveryWorkers; // At least one transaction per processor + } + // Tx configuration - not configurable yet if (m_loadExtraParams) { UPDATE_BOOL_CFG(m_abortBufferEnable, "tx_abort_buffers_enable", true); @@ -983,7 +1058,7 @@ void MOTConfiguration::LoadConfig() // log configuration UPDATE_USER_CFG(m_logLevel, "log_level", DEFAULT_LOG_LEVEL); - SetGlobalLogLevel(m_logLevel); + (void)SetGlobalLogLevel(m_logLevel); if (m_loadExtraParams) { UPDATE_USER_CFG(m_numaErrorsLogLevel, "numa_errors_log_level", DEFAULT_NUMA_ERRORS_LOG_LEVEL); UPDATE_USER_CFG(m_numaWarningsLogLevel, "numa_warnings_log_level", DEFAULT_NUMA_WARNINGS_LOG_LEVEL); @@ -994,7 +1069,6 @@ void MOTConfiguration::LoadConfig() LoadMemConfig(); // GC configuration - UPDATE_BOOL_CFG(m_gcEnable, "enable_gc", DEFAULT_GC_ENABLE); UPDATE_ABS_MEM_CFG(m_gcReclaimThresholdBytes, "reclaim_threshold", DEFAULT_GC_RECLAIM_THRESHOLD, @@ -1015,10 +1089,19 @@ void MOTConfiguration::LoadConfig() // JIT configuration UPDATE_BOOL_CFG(m_enableCodegen, "enable_mot_codegen", DEFAULT_ENABLE_MOT_CODEGEN); - UPDATE_BOOL_CFG(m_forcePseudoCodegen, "force_mot_pseudo_codegen", DEFAULT_FORCE_MOT_PSEUDO_CODEGEN); + UPDATE_BOOL_CFG(m_enableQueryCodegen, "enable_mot_query_codegen", DEFAULT_ENABLE_MOT_QUERY_CODEGEN); + UPDATE_BOOL_CFG(m_enableSPCodegen, "enable_mot_sp_codegen", DEFAULT_ENABLE_MOT_SP_CODEGEN); + + if (m_loadExtraParams) { + UPDATE_STRING_CFG(m_spCodegenAllowed, "mot_sp_codegen_allowed", DEFAULT_MOT_SP_CODEGEN_ALLOWED); + UPDATE_STRING_CFG(m_spCodegenProhibited, "mot_sp_codegen_prohibited", DEFAULT_MOT_SP_CODEGEN_PROHIBITED); + UPDATE_STRING_CFG(m_pureSPCodegen, "mot_pure_sp_codegen", DEFAULT_MOT_PURE_SP_CODEGEN); + } + UPDATE_BOOL_CFG(m_enableCodegenPrint, "enable_mot_codegen_print", DEFAULT_ENABLE_MOT_CODEGEN_PRINT); UPDATE_INT_CFG( m_codegenLimit, "mot_codegen_limit", DEFAULT_MOT_CODEGEN_LIMIT, MIN_MOT_CODEGEN_LIMIT, MAX_MOT_CODEGEN_LIMIT); + UPDATE_BOOL_CFG(m_enableCodegenProfile, "enable_mot_codegen_profile", DEFAULT_ENABLE_MOT_CODEGEN_PROFILE); // storage configuration if (m_loadExtraParams) { @@ -1038,11 +1121,11 @@ void MOTConfiguration::LoadConfig() UPDATE_BOOL_CFG(m_runInternalConsistencyValidation, "internal_consistency_validation", DEFAULT_RUN_INTERNAL_CONSISTENCY_VALIDATION); + UPDATE_BOOL_CFG(m_runInternalMvccConsistencyValidation, + "internal_mvcc_consistency_validation", + DEFAULT_RUN_INTERNAL_CONSISTENCY_VALIDATION); } - // load component log levels - UpdateComponentLogLevel(); - MOT_LOG_TRACE("Main configuration loaded"); } @@ -1083,7 +1166,7 @@ void MOTConfiguration::UpdateMemConfigItem(uint64_t& oldValue, const char* name, UpdateIntConfigItem(oldValue, defaultValueBytes / scale, name, lowerBound, upperBound); } else { // now we carefully examine the value type - ConfigValue* cfgValue = (ConfigValue*)cfgItem; + const ConfigValue* cfgValue = (const ConfigValue*)cfgItem; if (cfgValue->IsIntegral()) { // only a number was specified, so it is interpreted as bytes uint64_t memoryValueBytes = @@ -1155,7 +1238,7 @@ void MOTConfiguration::UpdateTimeConfigItem(uint64_t& oldValue, const char* name UpdateIntConfigItem(oldValue, defaultValueUSecs / scale, name, lowerBound, upperBound); } else { // now we carefully examine the value type - ConfigValue* cfgValue = (ConfigValue*)cfgItem; + const ConfigValue* cfgValue = (const ConfigValue*)cfgItem; if (cfgValue->IsIntegral()) { // only a number was specified, so it is interpreted as micro-seconds uint64_t timeValueUSecs = cfg->GetIntegerConfigValue(name, defaultValueUSecs, m_suppressLog == 0); @@ -1212,7 +1295,7 @@ void MOTConfiguration::UpdateComponentLogLevel() componentName.c_str(), TypeFormatter::ToString(componentLevel, logLevelStr)); } - SetLogComponentLogLevel(componentName.c_str(), componentLevel); + (void)SetLogComponentLogLevel(componentName.c_str(), componentLevel); } // all configuration values are logger configuration pairs (loggerName=log_level) @@ -1238,7 +1321,7 @@ void MOTConfiguration::UpdateComponentLogLevel() componentName.c_str(), TypeFormatter::ToString(loggerLevel, logLevelStr)); } - SetLoggerLogLevel(componentName.c_str(), loggerName.c_str(), loggerLevel); + (void)SetLoggerLogLevel(componentName.c_str(), loggerName.c_str(), loggerLevel); } ++loggerItr; } @@ -1278,12 +1361,16 @@ int MOTConfiguration::ParseMemoryPercent(const char* memoryValue) MOT_LOG_WARN("Invalid memory value format: %s (percentage value not in the range [0, 100])", memoryValue); } else { mot_string extra(endptr + 1); - extra.trim(); - if (extra.length() != 0) { - MOT_LOG_WARN("Invalid memory value format: %s (trailing characters after %%)", memoryValue); + if (!extra.is_valid()) { + MOT_LOG_ERROR("Failed to allocate memory string: %s", endptr + 1); } else { - MOT_LOG_TRACE("Parsed percentage: %d%%", percent); - result = percent; + extra.trim(); + if (extra.length() != 0) { + MOT_LOG_WARN("Invalid memory value format: %s (trailing characters after %%)", memoryValue); + } else { + MOT_LOG_TRACE("Parsed percentage: %d%%", percent); + result = percent; + } } } return result; @@ -1314,7 +1401,7 @@ uint64_t MOTConfiguration::ParseMemoryPercentTotal(const char* memoryValue, uint memoryValueBytes = m_totalMemoryMb * MEGA_BYTE * percent / 100; if (m_suppressLog == 0) { MOT_LOG_INFO( - "Loaded %s: %d%% from total = %" PRIu64 " MB", cfgPath, percent, memoryValueBytes / 1024ul / 1024ul); + "Loaded %s: %d%% from total = %" PRIu64 " MB", cfgPath, percent, (memoryValueBytes / 1024ul) / 1024ul); } } else { MOT_LOG_WARN("Invalid %s memory format: illegal percent specification", cfgPath); @@ -1330,39 +1417,40 @@ uint64_t MOTConfiguration::ParseMemoryUnit(const char* memoryValue, uint64_t def if (endptr == memoryValue) { MOT_LOG_WARN("Invalid %s memory value format: %s (expecting value digits)", cfgPath, memoryValue); } else if (*endptr == 0) { - MOT_LOG_TRACE("Missing %s memory value units: bytes assumed", cfgPath, memoryValue); - memoryValueBytes = value; + MOT_LOG_TRACE("Missing %s memory value units: %s (kilobytes assumed)", cfgPath, memoryValue); + memoryValueBytes = ((uint64_t)value) * 1024; } else { // get unit type and convert to mega-bytes mot_string suffix(endptr); - suffix.trim(); - if (suffix.compare_no_case("TB") == 0) { - MOT_LOG_TRACE("Loaded %s: %u TB", cfgPath, value); - memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull * 1024ull * 1024ull; - } else if (suffix.compare_no_case("GB") == 0) { - MOT_LOG_TRACE("Loaded %s: %u GB", cfgPath, value); - memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull * 1024ull; - } else if (suffix.compare_no_case("MB") == 0) { - MOT_LOG_TRACE("Loaded %s: %u MB", cfgPath, value); - memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull; - } else if (suffix.compare_no_case("KB") == 0) { - MOT_LOG_TRACE("Loaded %s: %u KB", cfgPath, value); - memoryValueBytes = ((uint64_t)value) * 1024; - } else if (suffix.compare_no_case("bytes") == 0) { - MOT_LOG_TRACE("Loaded %s: %u bytes", cfgPath, value); - memoryValueBytes = value; + if (!suffix.is_valid()) { + MOT_LOG_ERROR("Failed to allocate memory string: %s", endptr); } else { - MOT_LOG_WARN("Invalid %s memory value format: %s (invalid unit specifier '%s' - should be one of TB, GB, " - "MB, KB or bytes)", - cfgPath, - memoryValue, - suffix.c_str()); + suffix.trim(); + if (suffix.compare_no_case("TB") == 0) { + MOT_LOG_TRACE("Loaded %s: %u TB", cfgPath, value); + memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull * 1024ull * 1024ull; + } else if (suffix.compare_no_case("GB") == 0) { + MOT_LOG_TRACE("Loaded %s: %u GB", cfgPath, value); + memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull * 1024ull; + } else if (suffix.compare_no_case("MB") == 0) { + MOT_LOG_TRACE("Loaded %s: %u MB", cfgPath, value); + memoryValueBytes = ((uint64_t)value) * 1024ull * 1024ull; + } else if (suffix.compare_no_case("KB") == 0) { + MOT_LOG_TRACE("Loaded %s: %u KB", cfgPath, value); + memoryValueBytes = ((uint64_t)value) * 1024; + } else { + MOT_LOG_WARN("Invalid %s memory value format: %s (invalid unit specifier '%s' - should be one of TB, " + "GB, MB or KB)", + cfgPath, + memoryValue, + suffix.c_str()); + } } } return memoryValueBytes; } -static inline bool IsTimeUnitDays(mot_string& suffix) +static inline bool IsTimeUnitDays(const mot_string& suffix) { if ((suffix.compare_no_case("d") == 0) || (suffix.compare_no_case("days") == 0) || (suffix.compare_no_case("day") == 0)) { @@ -1371,7 +1459,7 @@ static inline bool IsTimeUnitDays(mot_string& suffix) return false; } -static inline bool IsTimeUnitHours(mot_string& suffix) +static inline bool IsTimeUnitHours(const mot_string& suffix) { if ((suffix.compare_no_case("h") == 0) || (suffix.compare_no_case("hours") == 0) || (suffix.compare_no_case("hour") == 0)) { @@ -1380,7 +1468,7 @@ static inline bool IsTimeUnitHours(mot_string& suffix) return false; } -static inline bool IsTimeUnitMinutes(mot_string& suffix) +static inline bool IsTimeUnitMinutes(const mot_string& suffix) { if ((suffix.compare_no_case("m") == 0) || (suffix.compare_no_case("mins") == 0) || (suffix.compare_no_case("minutes") == 0) || (suffix.compare_no_case("min") == 0) || @@ -1390,7 +1478,7 @@ static inline bool IsTimeUnitMinutes(mot_string& suffix) return false; } -static inline bool IsTimeUnitSeconds(mot_string& suffix) +static inline bool IsTimeUnitSeconds(const mot_string& suffix) { if ((suffix.compare_no_case("s") == 0) || (suffix.compare_no_case("secs") == 0) || (suffix.compare_no_case("seconds") == 0) || (suffix.compare_no_case("sec") == 0) || @@ -1400,7 +1488,7 @@ static inline bool IsTimeUnitSeconds(mot_string& suffix) return false; } -static inline bool IsTimeUnitMilliSeconds(mot_string& suffix) +static inline bool IsTimeUnitMilliSeconds(const mot_string& suffix) { if ((suffix.compare_no_case("ms") == 0) || (suffix.compare_no_case("millis") == 0) || (suffix.compare_no_case("milliseconds") == 0) || (suffix.compare_no_case("milli") == 0) || @@ -1410,7 +1498,7 @@ static inline bool IsTimeUnitMilliSeconds(mot_string& suffix) return false; } -static inline bool IsTimeUnitMicroSeconds(mot_string& suffix) +static inline bool IsTimeUnitMicroSeconds(const mot_string& suffix) { if ((suffix.compare_no_case("us") == 0) || (suffix.compare_no_case("micros") == 0) || (suffix.compare_no_case("microseconds") == 0) || (suffix.compare_no_case("micro") == 0) || @@ -1428,35 +1516,37 @@ uint64_t MOTConfiguration::ParseTimeValueMicros(const char* timeValue, uint64_t if (endptr == timeValue) { MOT_LOG_WARN("Invalid %s time value format: %s (expecting value digits)", cfgPath, timeValue); } else if (*endptr == 0) { - MOT_LOG_WARN("Invalid %s time value format: %s (expecting unit type after value)", cfgPath, timeValue); + MOT_LOG_TRACE("Missing %s time value units: %s (milliseconds assumed)", cfgPath, timeValue); + timeValueMicros = ((uint64_t)value) * 1000ull; } else { // get unit type and convert to micro-seconds mot_string suffix(endptr); - suffix.trim(); - if (IsTimeUnitDays(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u days", cfgPath, value); - timeValueMicros = ((uint64_t)value) * 24ull * 60ull * 60ull * 1000ull * 1000ull; - } else if (IsTimeUnitHours(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u hours", cfgPath, value); - timeValueMicros = ((uint64_t)value) * 60ull * 60ull * 1000ull * 1000ull; - } else if (IsTimeUnitMinutes(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u minutes", cfgPath, value); - timeValueMicros = ((uint64_t)value) * 60ull * 1000ull * 1000ull; - } else if (IsTimeUnitSeconds(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u seconds", cfgPath, value); - timeValueMicros = ((uint64_t)value) * 1000ull * 1000ull; - } else if (IsTimeUnitMilliSeconds(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u milli-seconds", cfgPath, value); - timeValueMicros = ((uint64_t)value) * 1000ull; - } else if (IsTimeUnitMicroSeconds(suffix)) { - MOT_LOG_TRACE("Loaded %s: %u micro-seconds", cfgPath, value); - timeValueMicros = ((uint64_t)value); + if (!suffix.is_valid()) { + MOT_LOG_ERROR("Failed to allocate time unit string: %s", endptr); } else { - MOT_LOG_WARN("Invalid %s time value format: %s (invalid unit specifier '%s' - should be one of d, h, m, s, " - "ms or us)", - cfgPath, - timeValue, - suffix.c_str()); + suffix.trim(); + if (IsTimeUnitDays(suffix)) { + MOT_LOG_TRACE("Loaded %s: %u days", cfgPath, value); + timeValueMicros = ((uint64_t)value) * 24ull * 60ull * 60ull * 1000ull * 1000ull; + } else if (IsTimeUnitHours(suffix)) { + MOT_LOG_TRACE("Loaded %s: %u hours", cfgPath, value); + timeValueMicros = ((uint64_t)value) * 60ull * 60ull * 1000ull * 1000ull; + } else if (IsTimeUnitMinutes(suffix)) { + MOT_LOG_TRACE("Loaded %s: %u minutes", cfgPath, value); + timeValueMicros = ((uint64_t)value) * 60ull * 1000ull * 1000ull; + } else if (IsTimeUnitSeconds(suffix)) { + MOT_LOG_TRACE("Loaded %s: %u seconds", cfgPath, value); + timeValueMicros = ((uint64_t)value) * 1000ull * 1000ull; + } else if (IsTimeUnitMilliSeconds(suffix)) { + MOT_LOG_TRACE("Loaded %s: %u milli-seconds", cfgPath, value); + timeValueMicros = ((uint64_t)value) * 1000ull; + } else { + MOT_LOG_WARN("Invalid %s time value format: %s (invalid unit specifier '%s' - should be one of d, h, " + "m, s or ms)", + cfgPath, + timeValue, + suffix.c_str()); + } } } return timeValueMicros; diff --git a/src/gausskernel/storage/mot/core/system/mot_configuration.h b/src/gausskernel/storage/mot/core/system/mot_configuration.h index 65c33c73d..3e1c766c4 100644 --- a/src/gausskernel/storage/mot/core/system/mot_configuration.h +++ b/src/gausskernel/storage/mot/core/system/mot_configuration.h @@ -38,6 +38,7 @@ #include "index_defs.h" #include "mm_def.h" #include "mot_error.h" +#include "recovery_mode.h" namespace MOT { /** @typedef Mapping from CPU identifier to NUMA node identifier. */ @@ -52,7 +53,7 @@ typedef std::map> CpuMap; class MOTConfiguration : public IConfigChangeListener { public: MOTConfiguration(); - ~MOTConfiguration(); + ~MOTConfiguration() override; /** @var Initialize configuration singleton. */ void Initialize(); @@ -67,7 +68,7 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange() + void OnConfigChange() override { MOT_LOG_TRACE("Reloading configuration after change"); LoadConfig(); @@ -139,9 +140,6 @@ public: /** Determines the redo log handler type (not configurable, but derived). */ RedoLogHandlerType m_redoLogHandlerType; - /** Determines the number of asynchronous redo log buffer arrays. */ - uint32_t m_asyncRedoLogBufferArrayCount; - /**********************************************************************/ // Commit configuration /**********************************************************************/ @@ -176,6 +174,15 @@ public: // Recovery configuration /**********************************************************************/ + /** @var Specifies recovery type. */ + RecoveryMode m_recoveryMode; + + /** @var Specifies the number of recovery workers to spawn in case of parallel recovery. */ + uint32_t m_parallelRecoveryWorkers; + + /** @var Specifies parallel recovery's queue size. */ + uint32_t m_parallelRecoveryQueueSize; + /** @var Specifies the number of workers used to recover from checkpoint. */ uint32_t m_checkpointRecoveryWorkers; @@ -319,9 +326,6 @@ public: /**********************************************************************/ // Garbage Collection configuration /**********************************************************************/ - /** @var Enable/disable garbage collection. */ - bool m_gcEnable; - /** @var The threshold in bytes for reclamation to be triggered (per-thread). */ uint64_t m_gcReclaimThresholdBytes; @@ -337,15 +341,31 @@ public: /** @var Enable/disable JIT compilation and execution for planned queries. */ bool m_enableCodegen; - /** @var Specifies whether to force usage of TVM JIT compilation and execution. */ - bool m_forcePseudoCodegen; + /** @var Enable/disable JIT compilation and execution of prepared queries. */ + bool m_enableQueryCodegen; - /** @var Specifies whether to print emitted LLVM/TVM IR code for JIT-compiled queries. */ + /** @var Enable/disable JIT compilation and execution of stored procedures. */ + bool m_enableSPCodegen; + + /** @var List of regular expressions denoting which stored procedures can use JIT compilation. */ + std::string m_spCodegenAllowed; + + /** @var List of regular expressions denoting which stored procedures cannot use JIT compilation. */ + std::string m_spCodegenProhibited; + + /** @var White-list of stored procedures allowed to use JIT compilation even though they do not access MOT tables. + */ + std::string m_pureSPCodegen; + + /** @var Specifies whether to print emitted LLVM IR code for JIT-compiled queries. */ bool m_enableCodegenPrint; /** @var Limits the amount of JIT queries allowed per user session. */ uint32_t m_codegenLimit; + /** @var Specified whether to enable jitted functions profiling. */ + bool m_enableCodegenProfile; + /**********************************************************************/ // Storage configuration /**********************************************************************/ @@ -364,6 +384,9 @@ public: /** @var Specifies whether to run consistency validation checks after benchmark. */ bool m_runInternalConsistencyValidation; + /** @var Specifies whether to run consistency validation checks after benchmark. */ + bool m_runInternalMvccConsistencyValidation; + /** * @brief Retrieves the NUMA node for the given CPU. * @param cpu The logical identifier of the CPU. @@ -371,7 +394,7 @@ public: */ int GetCpuNode(int cpu) const; - uint16_t GetCoreByConnidFP(uint16_t cpu) const; + int GetCoreByConnidFP(int cpu) const; inline bool IsHyperThread() const { @@ -380,9 +403,11 @@ public: int GetMappedCore(int logicId) const; - void SetMaskToAllCoresinNumaSocket(cpu_set_t& mask, uint64_t threadId); + int GetCoreFromNumaNodeByIndex(int numaId, int logicId) const; - void SetMaskToAllCoresinNumaSocket2(cpu_set_t& mask, int nodeId); + void SetMaskToAllCoresinNumaSocketByCoreId(cpu_set_t& mask, int coreId); + + void SetMaskToAllCoresinNumaSocketByNodeId(cpu_set_t& mask, int nodeId); // class non-copy-able, non-assignable, non-movable /** @cond EXCLUDE_DOC */ @@ -392,10 +417,6 @@ public: MOTConfiguration& operator=(const MOTConfiguration&& orig) = delete; /** @endcond */ - /** @var Asynchronous redo-log buffer array bounds (exposed as public for external use). */ - static constexpr uint32_t MIN_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT = 8; - static constexpr uint32_t MAX_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT = 128; - /** @var Memory scaling constants (from bytes). */ static constexpr uint64_t SCALE_BYTES = 1; static constexpr uint64_t SCALE_KILO_BYTES = KILO_BYTE; @@ -416,9 +437,6 @@ public: /** @var Default redo log handler type. */ static constexpr RedoLogHandlerType DEFAULT_REDO_LOG_HANDLER_TYPE = RedoLogHandlerType::SYNC_REDO_LOG_HANDLER; - /** @var Default asynchronous redo log buffer array count. */ - static constexpr uint32_t DEFAULT_ASYNC_REDO_LOG_BUFFER_ARRAY_COUNT = 24; - /** @var Default enable group commit. */ static constexpr bool DEFAULT_ENABLE_GROUP_COMMIT = false; @@ -431,9 +449,9 @@ public: static constexpr const char* DEFAULT_GROUP_COMMIT_TIMEOUT = "10 ms"; /** @var Default group commit timeout in micro-seconds. */ - static constexpr uint64_t DEFAULT_GROUP_COMMIT_TIMEOUT_USEC = 10000; - static constexpr uint64_t MIN_GROUP_COMMIT_TIMEOUT_USEC = 100; - static constexpr uint64_t MAX_GROUP_COMMIT_TIMEOUT_USEC = 200000; // 200 ms + static constexpr uint64_t DEFAULT_GROUP_COMMIT_TIMEOUT_USEC = 10000; // 10 ms + static constexpr uint64_t MIN_GROUP_COMMIT_TIMEOUT_USEC = 1000; // 1 ms + static constexpr uint64_t MAX_GROUP_COMMIT_TIMEOUT_USEC = 200000; // 200 ms /** ------------------ Default Checkpoint Configuration ------------ */ /** @var Default enable checkpoint. */ @@ -462,6 +480,19 @@ public: static constexpr uint32_t MIN_CHECKPOINT_RECOVERY_WORKERS = 1; static constexpr uint32_t MAX_CHECKPOINT_RECOVERY_WORKERS = 1024; + /** @var Default recovery mode. */ + static constexpr RecoveryMode DEFAULT_RECOVERY_MODE = RecoveryMode::MTLS; + + /** @var Default number of workers used in parallel recovery. */ + static constexpr uint32_t DEFAULT_PARALLEL_RECOVERY_WORKERS = 5; + static constexpr uint32_t MIN_PARALLEL_RECOVERY_WORKERS = 1; + static constexpr uint32_t MAX_PARALLEL_RECOVERY_WORKERS = 1024; + + /** @var Default queue size for parallel recovery. */ + static constexpr uint32_t DEFAULT_PARALLEL_RECOVERY_QUEUE_SIZE = 512; + static constexpr uint32_t MIN_PARALLEL_RECOVERY_QUEUE_SIZE = 16; + static constexpr uint32_t MAX_PARALLEL_RECOVERY_QUEUE_SIZE = 4096; + /** @var Default enable log recovery statistics. */ static constexpr bool DEFAULT_ENABLE_LOG_RECOVERY_STATS = false; @@ -538,7 +569,7 @@ public: static constexpr uint32_t MAX_MAX_CONNECTIONS = UINT16_MAX; /** @var Default thread affinity policy. */ - static constexpr AffinityMode DEFAULT_AFFINITY_MODE = AffinityMode::FILL_PHYSICAL_FIRST; + static constexpr AffinityMode DEFAULT_AFFINITY_MODE = AffinityMode::EQUAL_PER_SOCKET; /** @var The default value for using lazy load scheme in the global chunk directory. */ static constexpr bool DEFAULT_LAZY_LOAD_CHUNK_DIRECTORY = true; @@ -643,18 +674,37 @@ public: /** ------------------ Default JIT Configuration ------------ */ /** @var Default enable JIT compilation and execution. */ - static constexpr bool DEFAULT_ENABLE_MOT_CODEGEN = true; + static constexpr bool DEFAULT_ENABLE_MOT_CODEGEN = false; - /* @var Default force usage of TVM although LLVM is supported on current platform. */ - static constexpr bool DEFAULT_FORCE_MOT_PSEUDO_CODEGEN = false; + /** @var Default enable JIT compilation and execution of prepared queries. */ + static constexpr bool DEFAULT_ENABLE_MOT_QUERY_CODEGEN = true; - /** @var Default enable printing of emitted LLVM/TVM IR code of JIT-compiled queries. */ + /** @var Default enable JIT compilation and execution of stored procedures. */ + static constexpr bool DEFAULT_ENABLE_MOT_SP_CODEGEN = true; + + /** @var Default value for the list of regular expressions denoting stored procedures that can use JIT compilation. + */ + static constexpr const char* DEFAULT_MOT_SP_CODEGEN_ALLOWED = "[a-zA-z0-9_]*"; + + /** @var Default value for the list of regular expressions denoting stored procedures that cannot use JIT + * compilation. + */ + static constexpr const char* DEFAULT_MOT_SP_CODEGEN_PROHIBITED = ""; + + /** @var Default white list of stored procedure that can use JIT compilation even though not accessing MOT tables. + */ + static constexpr const char* DEFAULT_MOT_PURE_SP_CODEGEN = "[a-zA-z0-9_]*"; + + /** @var Default enable printing of emitted LLVM IR code of JIT-compiled queries. */ static constexpr bool DEFAULT_ENABLE_MOT_CODEGEN_PRINT = false; - /** @vart Default limit for the amount of JIT queries allowed per user session. */ - static constexpr uint32_t DEFAULT_MOT_CODEGEN_LIMIT = 100; + /** @var Default limit for the amount of JIT queries allowed per user session. */ + static constexpr uint32_t DEFAULT_MOT_CODEGEN_LIMIT = 50000; static constexpr uint32_t MIN_MOT_CODEGEN_LIMIT = 1; - static constexpr uint32_t MAX_MOT_CODEGEN_LIMIT = 1000; + static constexpr uint32_t MAX_MOT_CODEGEN_LIMIT = 100000; + + /** @var Default enable profiling of jitted functions. */ + static constexpr bool DEFAULT_ENABLE_MOT_CODEGEN_PROFILE = true; /** ------------------ Default Storage Configuration ------------ */ /** @var The default allow index on null-able column. */ diff --git a/src/gausskernel/storage/mot/core/system/mot_engine.cpp b/src/gausskernel/storage/mot/core/system/mot_engine.cpp index 1cc29fb00..42ece5948 100644 --- a/src/gausskernel/storage/mot/core/system/mot_engine.cpp +++ b/src/gausskernel/storage/mot/core/system/mot_engine.cpp @@ -24,7 +24,6 @@ #include "mot_engine.h" #include "spin_lock.h" - #include "config_manager.h" #include "statistics_manager.h" #include "network_statistics.h" @@ -45,12 +44,13 @@ #include "cycles.h" #include "debug_utils.h" #include "recovery_manager_factory.h" +#include "csn_manager.h" // For mtSessionThreadInfo thread local #include "kvthread.hh" #include -#include +#include #include #include @@ -59,6 +59,8 @@ IMPLEMENT_CLASS_LOGGER(MOTEngine, System); MOTEngine* MOTEngine::m_engine = nullptr; +static CSNManager csnManager; + // Initialization helper macro (for system calls) #define CHECK_SYS_INIT_STATUS(rc, syscall, format, ...) \ if (rc != 0) { \ @@ -76,20 +78,32 @@ MOTEngine* MOTEngine::m_engine = nullptr; MOTEngine::MOTEngine() : m_initialized(false), m_recovering(false), + m_recoveringCleanup(false), m_sessionAffinity(0, 0, AffinityMode::AFFINITY_INVALID), m_taskAffinity(0, 0, AffinityMode::AFFINITY_INVALID), + m_csnManager(nullptr), m_softMemoryLimitReached(0), + m_padding(0), m_sessionManager(nullptr), m_tableManager(nullptr), m_surrogateKeyManager(nullptr), m_recoveryManager(nullptr), m_redoLogHandler(nullptr), - m_checkpointManager(nullptr) + m_checkpointManager(nullptr), + m_ddlSigFunc(nullptr) {} MOTEngine::~MOTEngine() { Destroy(); + m_csnManager = nullptr; + m_ddlSigFunc = nullptr; + m_sessionManager = nullptr; + m_tableManager = nullptr; + m_recoveryManager = nullptr; + m_surrogateKeyManager = nullptr; + m_redoLogHandler = nullptr; + m_checkpointManager = nullptr; } MOTEngine* MOTEngine::CreateInstance( @@ -249,9 +263,13 @@ void MOTEngine::Destroy() MOT_LOG_INFO("Shutdown: MOT Engine shutdown finished"); } -void MOTEngine::PrintCurrentWorkingDirectory() +void MOTEngine::PrintCurrentWorkingDirectory() const { char* cwd = (char*)malloc(sizeof(char) * PATH_MAX); + if (cwd == nullptr) { + MOT_LOG_TRACE("%s: Failed to allocate %d bytes", __FUNCTION__, PATH_MAX); + return; + } if (!getcwd(cwd, PATH_MAX)) { errno_t erc = strcpy_s(cwd, PATH_MAX, "N/A"); securec_check(erc, "\0", "\0"); @@ -260,7 +278,7 @@ void MOTEngine::PrintCurrentWorkingDirectory() free(cwd); } -void MOTEngine::PrintSystemInfo() +void MOTEngine::PrintSystemInfo() const { #ifdef MOT_DEBUG MOT_LOG_WARN("Running in DEBUG mode"); @@ -293,7 +311,7 @@ void MOTEngine::PrintSystemInfo() break; } } - fclose(f); + (void)fclose(f); } else { // default to /etc/os-release f = fopen("/etc/os-release", "r"); @@ -309,7 +327,7 @@ void MOTEngine::PrintSystemInfo() break; } } - fclose(f); + (void)fclose(f); } } } @@ -328,7 +346,12 @@ bool MOTEngine::InitializeCoreServices() MOTConfiguration& cfg = GetGlobalConfiguration(); m_sessionAffinity.Configure(cfg.m_numaNodes, cfg.m_coresPerCpu, cfg.m_sessionAffinityMode); m_taskAffinity.Configure(cfg.m_numaNodes, cfg.m_coresPerCpu, cfg.m_taskAffinityMode); - MOT_LOG_TRACE("Startup: Affinity initialized"); + MOT_LOG_INFO("Startup: Affinity initialized - numaNodes = %u, coresPerCpu = %u, sessionAffinityMode = %u, " + "taskAffinityMode = %u", + cfg.m_numaNodes, + cfg.m_coresPerCpu, + cfg.m_sessionAffinityMode, + cfg.m_taskAffinityMode); result = (InitThreadIdPool(cfg.m_maxThreads) == 0); CHECK_INIT_STATUS(result, "Failed to Initialize reusable thread identifier pool"); @@ -342,7 +365,10 @@ bool MOTEngine::InitializeCoreServices() result = (AllocThreadIdNumaHighest(0) != INVALID_THREAD_ID); CHECK_INIT_STATUS(result, "Failed to allocate loader thread identifier"); m_initCoreStack.push(INIT_LOADER_THREAD_ID_PHASE); - InitCurrentNumaNodeId(); + + result = InitCurrentNumaNodeId(); + CHECK_INIT_STATUS(result, "Failed to allocate loader thread NUMA node identifier"); + m_initCoreStack.push(INIT_LOADER_NODE_ID_PHASE); result = InitializeStatistics(); // Initialize statistics objects CHECK_INIT_STATUS(result, "Failed to Initialize statistics collection"); @@ -373,6 +399,9 @@ bool MOTEngine::InitializeCoreServices() result = InitializeDebugUtils(); CHECK_INIT_STATUS(result, "Failed to Initialize debug utilities"); m_initCoreStack.push(INIT_DEBUG_UTILS); + + SetCSNManager(&csnManager); + m_initCoreStack.push(INIT_CSN_MANAGER); } while (0); if (result) { @@ -391,14 +420,14 @@ bool MOTEngine::InitializeAppServices() MOT_LOG_TRACE("Startup: Initializing applicative services"); do { - result = InitializeRecoveryManager(); - CHECK_INIT_STATUS(result, "Failed to Initialize the recovery manager"); - m_initAppStack.push(INIT_RECOVERY_MANAGER_PHASE); - result = InitializeRedoLogHandler(); CHECK_INIT_STATUS(result, "Failed to Initialize the redo-log handler"); m_initAppStack.push(INIT_REDO_LOG_HANDLER_PHASE); + result = InitializeRecoveryManager(); + CHECK_INIT_STATUS(result, "Failed to Initialize the recovery manager"); + m_initAppStack.push(INIT_RECOVERY_MANAGER_PHASE); + // CheckpointManager uses RedoLogHandler, always make sure that // RedoLogHandler is initialize before CheckpointManager result = InitializeCheckpointManager(); @@ -475,8 +504,11 @@ void MOTEngine::DestroyCoreServices() DestroyStatistics(); break; - case INIT_LOADER_THREAD_ID_PHASE: + case INIT_LOADER_NODE_ID_PHASE: ClearCurrentNumaNodeId(); + break; + + case INIT_LOADER_THREAD_ID_PHASE: FreeThreadId(); break; @@ -511,14 +543,14 @@ void MOTEngine::DestroyAppServices() DestroyCheckpointManager(); break; - case INIT_REDO_LOG_HANDLER_PHASE: - DestroyRedoLogHandler(); - break; - case INIT_RECOVERY_MANAGER_PHASE: DestroyRecoveryManager(); break; + case INIT_REDO_LOG_HANDLER_PHASE: + DestroyRedoLogHandler(); + break; + default: break; } @@ -886,7 +918,6 @@ void MOTEngine::DestroyRecoveryManager() { MOT_LOG_INFO("Destroying the Recovery Manager"); if (m_recoveryManager != nullptr) { - m_recoveryManager->CleanUp(); delete m_recoveryManager; m_recoveryManager = nullptr; } @@ -935,12 +966,12 @@ void MOTEngine::OnCurrentThreadEnding() FreeThreadId(); } -uint64_t MOTEngine::GetCurrentMemoryConsumptionBytes() +uint64_t MOTEngine::GetCurrentMemoryConsumptionBytes() const { return MemGetCurrentGlobalMemoryBytes(); } -uint64_t MOTEngine::GetHardMemoryLimitBytes() +uint64_t MOTEngine::GetHardMemoryLimitBytes() const { return g_memGlobalCfg.m_maxGlobalMemoryMb * MEGA_BYTE; } diff --git a/src/gausskernel/storage/mot/core/system/mot_engine.h b/src/gausskernel/storage/mot/core/system/mot_engine.h index abcc9be09..21ebfd094 100644 --- a/src/gausskernel/storage/mot/core/system/mot_engine.h +++ b/src/gausskernel/storage/mot/core/system/mot_engine.h @@ -25,7 +25,7 @@ #ifndef MOT_ENGINE_H #define MOT_ENGINE_H -#include +#include #include #include #include @@ -33,7 +33,7 @@ #include #include "table.h" #include "affinity.h" -#include "commit_sequence_number.h" +#include "icsn_manager.h" #include "checkpoint_manager.h" #include "utilities.h" #include "redo_log_handler.h" @@ -44,7 +44,6 @@ #include "surrogate_key_manager.h" #include "gc_context.h" #include "mot_atomic_ops.h" -#include "inprocess_transactions.h" namespace MOT { class ConfigLoader; @@ -53,6 +52,26 @@ class RedoLogHandler; /** @typedef CpSigFunc Callback for notifying envelope that engine finished checkpoint. */ typedef void (*CpSigFunc)(void); +/** @enum Transactional DDL execution phase constants. */ +enum class TxnDDLPhase { + /** @var Denotes transaction DDL execution. Emitted for each DDL operation within the transaction. */ + TXN_DDL_PHASE_EXEC, + + /** @var Denotes transaction DDL commit. Emitted only for the entire transaction. */ + TXN_DDL_PHASE_COMMIT, + + /** + * @var Denotes transaction DDL rollback. Emitted on the relation level for create table and create index, and also + * for the entire transaction. + */ + TXN_DDL_PHASE_ROLLBACK, + + TXN_DDL_PHASE_POST_COMMIT_CLEANUP +}; + +/** @typedef DDLSigFunc Callback for being notified of DDL events in MOT tables. */ +typedef void (*DDLSigFunc)(uint64_t relationId, DDLAccessType event, TxnDDLPhase txnDdlPhase); + /** * @class MOTEngine * @brief The single entry point to the MOT engine. @@ -72,9 +91,7 @@ private: static MOTEngine* m_engine; public: - /**********************************************************************/ // Initialization/Termination/Configuration API - /**********************************************************************/ /** * @brief Creates the single MOT engine instance. Use this variant when you have no external configuration * loaders involved. @@ -152,9 +169,7 @@ public: m_taskAffinity.Configure(cfg.m_numaNodes, cfg.m_coresPerCpu, cfg.m_taskAffinityMode); } - /**********************************************************************/ // Misc API - /**********************************************************************/ /** @brief Retrieves the session manager. */ inline SessionManager* GetSessionManager() { @@ -201,14 +216,30 @@ public: } /** @brief Retrieves the commit sequence number manager. */ - inline CSNManager& GetCSNManager() + inline void SetCSNManager(ICSNManager* manager) { - return m_csnManager; + m_csnManager = manager; + } + + /** @brief Retrieves the commit sequence number manager. */ + inline ICSNManager& GetCSNManager() + { + return *m_csnManager; } inline uint64_t GetCurrentCSN() { - return m_csnManager.GetCurrentCSN(); + return m_csnManager->GetCurrentCSN(); + } + + inline uint64_t GetNextCSN() + { + return m_csnManager->GetNextCSN(); + } + + inline uint64_t GetGcEpoch() + { + return m_csnManager->GetGcEpoch(); } /** @brief Retrieves the transaction id manager. */ @@ -217,16 +248,21 @@ public: return m_txnIdManager; } - /** - * @brief Sets the CPU affinity of a thread. - * @param threadId The logical identifier of the thread. - * @param[out,opt] threadCore The resulting core identifier. - */ - void SetAffinity(uint64_t threadId, uint32_t* threadCore = nullptr) const; + /** @brief Installs DDL event listener. */ + inline void SetDDLCallback(DDLSigFunc ddlSigFunc) + { + m_ddlSigFunc = ddlSigFunc; + } + + /** @brief Notify of DDL events to external listeners. */ + inline void NotifyDDLEvent(uint64_t relationId, DDLAccessType event, TxnDDLPhase txnPhase) const + { + if (m_ddlSigFunc != nullptr) { + m_ddlSigFunc(relationId, event, txnPhase); + } + } - /**********************************************************************/ // Logging/Checkpoint API - /**********************************************************************/ inline CheckpointManager* GetCheckpointManager() { return m_checkpointManager; @@ -303,9 +339,7 @@ public: return result; } - /**********************************************************************/ // GC Context API - /**********************************************************************/ inline GcManager* GetGcContext(uint32_t threadId) { return m_gcContext.GetGcContext(threadId); @@ -316,19 +350,12 @@ public: return m_gcContext.SetGcContext(threadId, gcManager); } - /**********************************************************************/ // Recovery API - /**********************************************************************/ inline IRecoveryManager* GetRecoveryManager() { return m_recoveryManager; } - inline InProcessTransactions& GetInProcessTransactions() - { - return m_inProcessTransactions; - } - inline bool CreateRecoverySessionContext() { MOT_ASSERT(m_recoveryManager); @@ -351,29 +378,43 @@ public: inline void DestroyRecoverySessionContext() { SessionContext* ctx = MOT_GET_CURRENT_SESSION_CONTEXT(); - MOT_ASSERT(ctx); if (ctx != nullptr) { m_sessionManager->DestroySessionContext(ctx); } OnCurrentThreadEnding(); } + inline void SetRecoveryStatus(bool value) + { + MOT_ATOMIC_STORE(m_recovering, value); + } + + inline void SetRecoveryCleanupStatus(bool value) + { + MOT_ATOMIC_STORE(m_recoveringCleanup, value); + } + inline bool StartRecovery() { - m_recovering = true; + SetRecoveryStatus(true); if (!CreateRecoverySessionContext()) { return false; } MOT_ASSERT(m_recoveryManager); - return m_recoveryManager->RecoverDbStart(); + if (!m_recoveryManager->RecoverDbStart()) { + return false; + } + return true; } inline bool EndRecovery() { MOT_ASSERT(m_recoveryManager); + SetRecoveryCleanupStatus(true); bool status = m_recoveryManager->RecoverDbEnd(); DestroyRecoverySessionContext(); - m_recovering = false; + SetRecoveryCleanupStatus(false); + SetRecoveryStatus(false); return status; } @@ -382,9 +423,12 @@ public: return m_recovering; } - /**********************************************************************/ + bool IsRecoveryPerformingCleanup() const + { + return MOT_ATOMIC_LOAD(m_recoveringCleanup); + } + // Memory Limit API - /**********************************************************************/ /** @brief Queries if soft memory limit was reached. */ inline bool IsSoftMemoryLimitReached() const { @@ -407,20 +451,20 @@ public: * @brief Retrieves the current memory consumption (on all chunk pools). * @return The total memory consumption in bytes or zero if failed. */ - uint64_t GetCurrentMemoryConsumptionBytes(); + uint64_t GetCurrentMemoryConsumptionBytes() const; /** @brief Retrieves the configured maximum memory consumption (on all chunk pools). */ - uint64_t GetHardMemoryLimitBytes(); + uint64_t GetHardMemoryLimitBytes() const; private: /** @brief Safe cleanup of all Initialized resources. */ void Destroy(); /** @brief Print startup information. */ - void PrintCurrentWorkingDirectory(); + void PrintCurrentWorkingDirectory() const; /** @brief Print startup information. */ - void PrintSystemInfo(); + void PrintSystemInfo() const; /** * @brief Initializes all core services in the engine. @@ -518,6 +562,9 @@ private: /** @var Specifies whether the engine is in recovery mode. */ bool m_recovering; + /** @var Specifies whether the engine is in recovery mode and performing final cleanups. */ + bool m_recoveringCleanup; + /** @var Auxiliary structure to compute affinity for user sessions (when thread-pool is off). */ Affinity m_sessionAffinity; @@ -525,7 +572,7 @@ private: Affinity m_taskAffinity; /** @var The commit sequence number handler (CSN). */ - CSNManager m_csnManager; + ICSNManager* m_csnManager; /** @var The transaction id manager. */ TransactionIdManager m_txnIdManager; @@ -557,8 +604,8 @@ private: /** @var The checkpoint manager. */ CheckpointManager* m_checkpointManager; - /** @var The In-ProcessTransactions container. */ - InProcessTransactions m_inProcessTransactions; + /** @var DDL event */ + DDLSigFunc m_ddlSigFunc; // record initialization failure point, so that Destroy can be called at any point of failure enum InitPhase { @@ -575,6 +622,7 @@ private: INIT_THREAD_ID_POOL_PHASE, INIT_CONNECTION_ID_POOL_PHASE, INIT_LOADER_THREAD_ID_PHASE, + INIT_LOADER_NODE_ID_PHASE, INIT_STATISTICS_PHASE, INIT_MM_PHASE, INIT_SESSION_MANAGER_PHASE, @@ -582,14 +630,15 @@ private: INIT_SURROGATE_KEY_MANAGER_PHASE, INIT_GC_PHASE, INIT_DEBUG_UTILS, + INIT_CSN_MANAGER, INIT_CORE_DONE }; stack m_initCoreStack; enum InitAppPhase { INIT_APP_START, - INIT_RECOVERY_MANAGER_PHASE, INIT_REDO_LOG_HANDLER_PHASE, + INIT_RECOVERY_MANAGER_PHASE, INIT_CHECKPOINT_MANAGER_PHASE, INIT_APP_DONE }; @@ -622,7 +671,7 @@ inline Affinity& GetTaskAffinity() } /** @brief Retrieves the CSN manager. */ -inline CSNManager& GetCSNManager() +inline ICSNManager& GetCSNManager() { return MOTEngine::GetInstance()->GetCSNManager(); } diff --git a/src/gausskernel/storage/mot/core/system/mot_error.cpp b/src/gausskernel/storage/mot/core/system/mot_error.cpp index 3303f2b19..3f24413c1 100644 --- a/src/gausskernel/storage/mot/core/system/mot_error.cpp +++ b/src/gausskernel/storage/mot/core/system/mot_error.cpp @@ -28,8 +28,8 @@ #include "postgres.h" #include "knl/knl_thread.h" -#include -#include +#include +#include namespace MOT { DECLARE_LOGGER(Error, System) @@ -106,6 +106,12 @@ extern const char* ErrorCodeToString(int errorCode) return "Invalid memory allocation size"; case MOT_ERROR_INDEX_OUT_OF_RANGE: return "Index out of range"; + case MOT_ERROR_INVALID_STATE: + return "Invalid state"; + case MOT_ERROR_CONCURRENT_MODIFICATION: + return "Concurrent modification"; + case MOT_ERROR_STATEMENT_CANCELED: + return "Statement canceled due to user request"; default: return "Error code unknown"; } @@ -154,9 +160,9 @@ extern void PushErrorV(int errorCode, int severity, const char* file, int line, va_list args2; va_copy(args2, args); - errno_t erc = - vsnprintf_s(errorFrame->m_errorMessage, MOT_MAX_ERROR_MESSAGE, MOT_MAX_ERROR_MESSAGE - 1, format, args2); + errno_t erc = vsnprintf_truncated_s(errorFrame->m_errorMessage, MOT_MAX_ERROR_MESSAGE, format, args2); securec_check_ss(erc, "\0", "\0"); + errorFrame->m_errorMessage[MOT_MAX_ERROR_MESSAGE - 1] = 0; ++errorFrameCount; } } @@ -179,31 +185,29 @@ extern void PushSystemError(int errorCode, int severity, const char* file, int l #if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE strerror_r(errorCode, errbuf, bufSize); - erc = snprintf_s(errorFrame->m_errorMessage, + erc = snprintf_truncated_s(errorFrame->m_errorMessage, MOT_MAX_ERROR_MESSAGE, - MOT_MAX_ERROR_MESSAGE - 1, "System call %s() failed: %s (error code: %d)", systemCall, errbuf, errorCode); #else - erc = snprintf_s(errorFrame->m_errorMessage, + erc = snprintf_truncated_s(errorFrame->m_errorMessage, MOT_MAX_ERROR_MESSAGE, - MOT_MAX_ERROR_MESSAGE - 1, "System call %s() failed: %s (error code: %d)", systemCall, strerror_r(errorCode, errbuf, bufSize), errorCode); securec_check_ss(erc, "\0", "\0"); #endif - + errorFrame->m_errorMessage[MOT_MAX_ERROR_MESSAGE - 1] = 0; ++errorFrameCount; } } static void PrintErrorFrame(ErrorFrame* errorFrame) { - fprintf(stderr, + (void)fprintf(stderr, "\nat %s() (%s:%d)\n" "\tEntity : %s\n" "\tContext : %s\n" @@ -227,7 +231,7 @@ extern void PrintErrorStack() for (int i = errorFrameCount - 1; i >= 0; --i) { PrintErrorFrame(&errorStack[i]); } - fprintf(stderr, "\n"); + (void)fprintf(stderr, "\n"); } extern void ClearErrorStack() @@ -246,6 +250,10 @@ extern RC ErrorToRC(int errorCode) return RC_MEMORY_ALLOCATION_ERROR; case MOT_ERROR_UNIQUE_VIOLATION: return RC_UNIQUE_VIOLATION; + case MOT_ERROR_CONCURRENT_MODIFICATION: + return RC_CONCURRENT_MODIFICATION; + case MOT_ERROR_STATEMENT_CANCELED: + return RC_STATEMENT_CANCELED; case MOT_ERROR_INVALID_CFG: case MOT_ERROR_INVALID_ARG: case MOT_ERROR_SYSTEM_FAILURE: @@ -253,6 +261,7 @@ extern RC ErrorToRC(int errorCode) case MOT_ERROR_INTERNAL: case MOT_ERROR_RESOURCE_UNAVAILABLE: case MOT_ERROR_INVALID_MEMORY_SIZE: + case MOT_ERROR_INVALID_STATE: default: return RC_ERROR; } diff --git a/src/gausskernel/storage/mot/core/system/mot_error.h b/src/gausskernel/storage/mot/core/system/mot_error.h index 5e7a7da43..8a0864be2 100644 --- a/src/gausskernel/storage/mot/core/system/mot_error.h +++ b/src/gausskernel/storage/mot/core/system/mot_error.h @@ -22,14 +22,14 @@ * ------------------------------------------------------------------------- */ -#ifndef MM_ERROR_H -#define MM_ERROR_H +#ifndef MOT_ERROR_H +#define MOT_ERROR_H #include "mot_error_codes.h" #include "global.h" #include "mot_log.h" -#include +#include namespace MOT { /** @@ -228,4 +228,4 @@ extern RC ErrorToRC(int errorCode); #define MOT_REPORT_SYSTEM_PANIC(systemCall, context, format, ...) \ MOT_REPORT_SYSTEM_PANIC_CODE(errno, systemCall, context, format, ##__VA_ARGS__) -#endif /* MM_ERROR_H */ +#endif /* MOT_ERROR_H */ diff --git a/src/gausskernel/storage/mot/core/system/mot_error_codes.h b/src/gausskernel/storage/mot/core/system/mot_error_codes.h index 671d107d7..d7e905059 100644 --- a/src/gausskernel/storage/mot/core/system/mot_error_codes.h +++ b/src/gausskernel/storage/mot/core/system/mot_error_codes.h @@ -92,4 +92,10 @@ /** @define Error code denoting system is in invalid state. */ #define MOT_ERROR_INVALID_STATE 11 +/** @define Error code denoting concurrent modification. */ +#define MOT_ERROR_CONCURRENT_MODIFICATION 12 + +/** @define Error code denoting cancel request by user due to statement timeout. */ +#define MOT_ERROR_STATEMENT_CANCELED 13 + #endif /* MOT_ERROR_CODES_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.cpp b/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.cpp new file mode 100644 index 000000000..ab3e16832 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * base_recovery_manager.cpp + * Implements the shared functionality for all recovery managers + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mot_engine.h" +#include "checkpoint_utils.h" +#include "checkpoint_manager.h" +#include "base_recovery_manager.h" + +namespace MOT { +DECLARE_LOGGER(BaseRecoveryManager, Recovery); +IMPLEMENT_CLASS_LOGGER(RecoveryStats, Recovery); + +bool BaseRecoveryManager::Initialize() +{ + if (CheckpointControlFile::GetCtrlFile() == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate ctrlfile object"); + return false; + } + + if (!m_surrogateState.Init()) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate surrogate state object"); + return false; + } + + if (m_enableLogStats) { + m_recoveryTableStats = new (std::nothrow) RecoveryStats(); + if (m_recoveryTableStats == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate statistics object"); + return false; + } + } + + m_initialized = true; + return m_initialized; +} + +bool BaseRecoveryManager::RecoverDbStart() +{ + if (m_recoverFromCkptDone) { + /* + * This is switchover case. + * In case of CASCADE switchover (CASCADE node switchover with STANDBY node), we need to + * set the correct m_lsn to make sure that we skip any redo which are already replayed. + * After switchover, envelope will start sync from the previous ckpt position. + * In MOT engine, we should skip any redo before m_lastReplayLsn. + */ + if (m_lsn < m_lastReplayLsn.load()) { + m_lsn = m_lastReplayLsn.load(); + } + return true; + } + + if (!m_checkpointRecovery.Recover()) { + return false; + } + + /* + * Set both m_lsn and m_lastReplayLsn. m_lastReplayLsn is needed as it will also be stored in control file + * during the checkpoint in standby node. + */ + SetLsn(m_checkpointRecovery.GetLsn()); + SetLastReplayLsn(m_checkpointRecovery.GetLsn()); + SetMaxCsn(m_checkpointRecovery.GetMaxCsn()); + if (m_maxCsn) { + GetCSNManager().SetCSN(m_maxCsn); + } + SetMaxTransactionId(m_checkpointRecovery.GetMaxTransactionId()); + m_recoverFromCkptDone = true; + return true; +} + +bool BaseRecoveryManager::RecoverDbEnd() +{ + ApplySurrogate(); + if (m_maxTransactionId) { + GetTxnIdManager().SetId(m_maxTransactionId); + } + + if (m_recoveryTableStats != nullptr) { + m_recoveryTableStats->Print(); + m_recoveryTableStats->Clear(); + } + + if (IsErrorSet()) { + MOT_LOG_ERROR("RecoverDbEnd: Recovery failed!"); + return false; + } + + MOT_LOG_INFO("RecoverDbEnd: Recovery finished successfully (CSN: %lu, TxnId: %lu)", m_maxCsn, m_maxTransactionId); + return true; +} + +bool BaseRecoveryManager::ApplyRedoLog(uint64_t redoLsn, char* data, size_t len) +{ + if (redoLsn <= m_lsn) { + // ignore old redo records which are prior to our checkpoint LSN + MOT_LOG_INFO("ApplyRedoLog - ignoring old redo record. Checkpoint LSN: %lu, redo LSN: %lu", m_lsn, redoLsn); + return true; + } + return ApplyLogSegmentData(data, len, redoLsn); +} + +bool BaseRecoveryManager::Is1VCCLogSegment(LogSegment* segment) +{ + MOT_ASSERT(segment); + if (segment != nullptr) { + return (segment->m_controlBlock.m_opCode == OperationCode::COMMIT_TX_1VCC || + segment->m_controlBlock.m_opCode == OperationCode::PARTIAL_REDO_TX_1VCC); + } + return false; +} + +bool BaseRecoveryManager::IsMotOnlyTransaction(LogSegment* segment) +{ + MOT_ASSERT(segment); + if (segment != nullptr) { + return (segment->m_controlBlock.m_opCode == OperationCode::COMMIT_TX || + segment->m_controlBlock.m_opCode == OperationCode::PARTIAL_REDO_TX || + segment->m_controlBlock.m_externalTransactionId == INVALID_TRANSACTION_ID); // 1VCC log segment + } + return false; +} + +bool BaseRecoveryManager::IsDDLTransaction(LogSegment* segment) +{ + MOT_ASSERT(segment); + if (segment != nullptr) { + return ( + segment->m_controlBlock.m_opCode == OperationCode::COMMIT_DDL_TX || + segment->m_controlBlock.m_opCode == OperationCode::PARTIAL_REDO_DDL_TX || + (Is1VCCLogSegment(segment) && segment->m_controlBlock.m_externalTransactionId != INVALID_TRANSACTION_ID)); + } + return false; +} + +bool BaseRecoveryManager::HasUpdateIndexColumn(LogSegment* segment) const +{ + MOT_ASSERT(segment); + if (segment != nullptr) { + return static_cast(segment->m_controlBlock.m_flags & EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG); + } + return false; +} + +bool BaseRecoveryManager::IsTransactionIdCommitted(uint64_t xid) +{ + MOT_ASSERT(m_clogCallback); + if (m_clogCallback != nullptr) { + return ((*m_clogCallback)(xid) == TXN_COMMITED); + } + return false; +} + +void BaseRecoveryManager::AddSurrogateArrayToList(SurrogateState& surrogate) +{ + std::lock_guard lock(m_surrogateListLock); + if (!surrogate.IsEmpty()) { + uint64_t* newArray = (uint64_t*)malloc(surrogate.GetMaxConnections() * sizeof(uint64_t)); + if (newArray != nullptr) { + errno_t erc = memcpy_s(newArray, + surrogate.GetMaxConnections() * sizeof(uint64_t), + surrogate.GetArray(), + surrogate.GetMaxConnections() * sizeof(uint64_t)); + securec_check(erc, "\0", "\0"); + m_surrogateList.push_back(newArray); + } + } +} + +void BaseRecoveryManager::ApplySurrogate() +{ + // merge and apply all SurrogateState maps + SurrogateState::Merge(m_surrogateList, m_surrogateState); + m_surrogateList.clear(); + + if (m_surrogateState.IsEmpty()) { + return; + } + + const uint64_t* array = m_surrogateState.GetArray(); + for (int i = 0; i < m_maxConnections; i++) { + GetSurrogateKeyManager()->SetSurrogateSlot(i, array[i]); + } +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.h b/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.h new file mode 100644 index 000000000..0505bd954 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.h @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * base_recovery_manager.h + * Implements the shared functionality for all recovery managers + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/base_recovery_manager.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef BASE_RECOVERY_MANAGER_H +#define BASE_RECOVERY_MANAGER_H + +#include +#include "checkpoint_ctrlfile.h" +#include "mot_configuration.h" +#include "irecovery_manager.h" +#include "recovery_stats.h" +#include "surrogate_state.h" +#include "checkpoint_recovery.h" +#include "recovery_ops.h" + +namespace MOT { + +/** + * @class BaseRecoveryManager + * @brief Shared recovery functionality + */ +class BaseRecoveryManager : public IRecoveryManager { +public: + BaseRecoveryManager() + : m_initialized(false), + m_lsn(0), + m_lastReplayLsn(0), + m_errorSet(false), + m_maxCsn(INVALID_CSN), + m_maxTransactionId(INVALID_TRANSACTION_ID), + m_clogCallback(nullptr), + m_recoverFromCkptDone(false), + m_maxConnections(GetGlobalConfiguration().m_maxConnections), + m_enableLogStats(GetGlobalConfiguration().m_enableLogRecoveryStats), + m_recoveryTableStats(nullptr) + {} + + ~BaseRecoveryManager() override + { + if (m_recoveryTableStats != nullptr) { + delete m_recoveryTableStats; + m_recoveryTableStats = nullptr; + } + m_clogCallback = nullptr; + } + + /** + * @brief Performs the necessary tasks to initialize the object. + * @return Boolean value denoting success or failure. + */ + bool Initialize() override; + + void SetCommitLogCallback(CommitLogStatusCallback clogCallback) override + { + m_clogCallback = clogCallback; + } + + /** + * @brief Starts the recovery process. + * @return Boolean value denoting success or failure. + */ + bool RecoverDbStart() override; + + /** + * @brief Performs post recovery operations. + * @return Boolean value denoting success or failure. + */ + bool RecoverDbEnd() override; + + /** + * @brief attempts to insert a data chunk into the in-process + * transactions map and operate on it. Checks that the redo lsn is after the + * checkpoint snapshot lsn taken. Redo records that are prior snapshot are + * ignored. + * @return Boolean value denoting success or failure. + */ + bool ApplyRedoLog(uint64_t redoLsn, char* data, size_t len) override; + + /** + * @brief Checks if the given log segment is an 1VCC one (deprecated). + * @param segment Log segment to check. + * @return Boolean value that is true if the log segment is an 1VCC one. + */ + static bool Is1VCCLogSegment(LogSegment* segment); + + /** + * @brief checks if the transaction is an mot only one. + * @param segment the log segment to check. + * @return Boolean value that is true if the txn is an mot only one. + */ + static bool IsMotOnlyTransaction(LogSegment* segment); + + /** + * @brief Checks if the transaction has DDL operations. + * @param segment Log segment to check. + * @return Boolean value that is true if the txn has DDL operations. + */ + static bool IsDDLTransaction(LogSegment* segment); + + /** + * @brief Checks if the transaction has update index column operations. + * @param segment Log segment to check. + * @return Boolean value that is true if the txn has these operations. + */ + bool HasUpdateIndexColumn(LogSegment* segment) const; + + /** + * @brief checks the clog if a transaction id is in commit state. + * @param xid the transaction id. + * @return Boolean value that is true if the transaction is committed. + */ + virtual bool IsTransactionIdCommitted(uint64_t xid); + + void AddSurrogateArrayToList(MOT::SurrogateState& surrogate) override; + + void ApplySurrogate(); + + /** + * @brief Sets the flag to indicate an error in recovery. + */ + void SetError() override + { + m_errorSet.store(true); + } + + bool IsErrorSet() const override + { + return m_errorSet.load(); + } + + void SetLsn(uint64_t lsn) + { + m_lsn = lsn; + } + + void SetLastReplayLsn(uint64_t replayLsn) override + { + if (m_lastReplayLsn.load() < replayLsn) { + m_lastReplayLsn.store(replayLsn); + } + } + + uint64_t GetLastReplayLsn() const override + { + return m_lastReplayLsn.load(); + } + + void SetMaxCsn(uint64_t csn) override + { + if (m_maxCsn < csn) { + m_maxCsn = csn; + } + } + + inline uint64_t GetMaxTransactionId() const + { + return m_maxTransactionId; + } + + inline void SetMaxTransactionId(uint64_t id) + { + if (m_maxTransactionId < id) { + m_maxTransactionId = id; + } + } + + void LogInsert(uint64_t tableId) override + { + if (m_recoveryTableStats != nullptr) { + m_recoveryTableStats->IncInsert(tableId); + } + } + + void LogUpdate(uint64_t tableId) override + { + if (m_recoveryTableStats != nullptr) { + m_recoveryTableStats->IncUpdate(tableId); + } + } + + void LogDelete(uint64_t tableId) override + { + if (m_recoveryTableStats != nullptr) { + m_recoveryTableStats->IncDelete(tableId); + } + } + + void LogCommit() override + { + if (m_recoveryTableStats != nullptr) { + m_recoveryTableStats->IncCommit(); + } + } + +protected: + bool m_initialized; + uint64_t m_lsn; + std::atomic m_lastReplayLsn; + std::mutex m_surrogateListLock; + std::list m_surrogateList; + SurrogateState m_surrogateState; + std::atomic m_errorSet; + uint64_t m_maxCsn; + uint64_t m_maxTransactionId; + CheckpointRecovery m_checkpointRecovery; + +private: + /** + * @brief attempts to insert a data chunk into the in-process + * transactions map and operate on it + * @return Boolean value denoting success or failure. + */ + virtual bool ApplyLogSegmentData(char* data, size_t len, uint64_t replayLsn) = 0; + + CommitLogStatusCallback m_clogCallback; + bool m_recoverFromCkptDone; + uint16_t m_maxConnections; + bool m_enableLogStats; + RecoveryStats* m_recoveryTableStats; +}; + +} // namespace MOT + +#endif // BASE_RECOVERY_MANAGER_H diff --git a/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.cpp b/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.cpp index 960a08a38..da9a04afb 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.cpp +++ b/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.cpp @@ -25,13 +25,14 @@ #include #include "mot_engine.h" #include "checkpoint_recovery.h" -#include "checkpoint_utils.h" #include "irecovery_manager.h" #include "redo_log_transaction_iterator.h" namespace MOT { DECLARE_LOGGER(CheckpointRecovery, Recovery); +static const char* const CKPT_RECOVERY_WORKER_NAME = "CheckpointRecoveryWorker"; + bool CheckpointRecovery::Recover() { MOT::MOTEngine* engine = MOT::MOTEngine::GetInstance(); @@ -53,8 +54,13 @@ bool CheckpointRecovery::Recover() return false; } - if (CheckpointControlFile::GetCtrlFile()->GetId() == CheckpointControlFile::invalidId) { - m_checkpointId = CheckpointControlFile::invalidId; // no mot control was found. + if (CheckpointControlFile::GetCtrlFile()->GetMetaVersion() < METADATA_VER_MVCC) { + MOT_LOG_INFO("CheckpointRecovery: upgrading from a pre MVCC version"); + m_preMvccUpgrade = true; + } + + if (CheckpointControlFile::GetCtrlFile()->GetId() == CheckpointControlFile::INVALID_ID) { + m_checkpointId = CheckpointControlFile::INVALID_ID; // no mot control was found. } else { if (!IsCheckpointValid(CheckpointControlFile::GetCtrlFile()->GetId())) { MOT_LOG_ERROR("CheckpointRecovery: no valid checkpoint exist"); @@ -66,7 +72,7 @@ bool CheckpointRecovery::Recover() m_lastReplayLsn = CheckpointControlFile::GetCtrlFile()->GetLastReplayLsn(); } - if (m_checkpointId != CheckpointControlFile::invalidId) { + if (m_checkpointId != CheckpointControlFile::INVALID_ID) { if (m_lsn >= m_lastReplayLsn) { MOT_LOG_INFO("CheckpointRecovery LSN Check: will use the LSN (%lu), ignoring the lastReplayLSN (%lu)", m_lsn, @@ -84,6 +90,8 @@ bool CheckpointRecovery::Recover() return false; } + m_maxTransactionId = CheckpointControlFile::GetCtrlFile()->GetMaxTransactionId(); + int taskFillStat = FillTasksFromMapFile(); if (taskFillStat < 0) { MOT_LOG_ERROR("CheckpointRecovery:: failed to read map file"); @@ -106,25 +114,33 @@ bool CheckpointRecovery::Recover() } if (m_errorSet) { - MOT_LOG_ERROR("Checkpoint recovery failed! error: %u:%s", m_errorCode, RcToString(m_errorCode)); + MOT_LOG_ERROR("Checkpoint recovery failed! Error: %u (%s)", m_errorCode, RcToString(m_errorCode)); return false; } } - if (!RecoverInProcessTxns()) { - MOT_LOG_ERROR("Failed to recover the in-process transactions from the checkpoint"); + if (!RecoverInProcessData()) { + MOT_LOG_ERROR("Failed to recover in process txn data"); return false; } + /* + * m_lastReplayLsn might be updated during RecoverInProcessData (if any), but even in that case it should always + * satisfy the condition (m_lsn >= m_lastReplayLsn). + */ + MOT_ASSERT(m_lsn >= m_lastReplayLsn); + /* * Set the current valid id in the checkpoint manager in case * we will need to retrieve it before a new checkpoint is created. */ engine->GetCheckpointManager()->SetId(m_checkpointId); - MOT_LOG_INFO("Checkpoint Recovery: finished recovering %lu tables from checkpoint id: %lu", + MOT_LOG_INFO("Checkpoint Recovery: Finished recovering %lu tables from checkpoint [%lu:%lu:%lu]", m_tableIds.size(), - m_checkpointId); + m_checkpointId, + m_lsn, + m_lastReplayLsn); m_tableIds.clear(); MOTEngine::GetInstance()->GetCheckpointManager()->RemoveOldCheckpoints(m_checkpointId); @@ -133,28 +149,29 @@ bool CheckpointRecovery::Recover() bool CheckpointRecovery::PerformRecovery() { - MOT_LOG_INFO("CheckpointRecovery: starting to recover %lu tables from checkpoint id: %lu", + MOT_LOG_INFO("CheckpointRecovery: Starting to recover %lu tables using %u workers from checkpoint [%lu]", m_tableIds.size(), + m_numWorkers, m_checkpointId); - for (auto it = m_tableIds.begin(); it != m_tableIds.end(); ++it) { + for (auto it = m_tableIds.begin(); it != m_tableIds.end(); (void)++it) { if (!RecoverTableMetadata(*it)) { - MOT_LOG_ERROR("CheckpointRecovery: failed to recover table metadata for table %u", *it); + MOT_LOG_ERROR("CheckpointRecovery: Failed to recover table metadata for table %u", *it); return false; } } std::vector threadPool; for (uint32_t i = 0; i < m_numWorkers; ++i) { - threadPool.push_back(std::thread(CheckpointRecoveryWorker, this)); + threadPool.push_back(std::thread(CheckpointRecoveryWorker, i, this)); } - MOT_LOG_DEBUG("CheckpointRecovery: waiting for all tasks to finish"); - while (HaveTasks() && m_stopWorkers == false) { - sleep(1); + MOT_LOG_DEBUG("CheckpointRecovery: Waiting for all tasks to finish"); + while (!AllTasksDone() && !m_stopWorkers) { + (void)sleep(1); } - MOT_LOG_DEBUG("CheckpointRecovery: tasks finished (%s)", m_errorSet ? "error" : "ok"); + MOT_LOG_DEBUG("CheckpointRecovery: Tasks finished (%s)", m_errorSet ? "error" : "ok"); for (auto& worker : threadPool) { if (worker.joinable()) { worker.join(); @@ -166,7 +183,7 @@ bool CheckpointRecovery::PerformRecovery() int CheckpointRecovery::FillTasksFromMapFile() { - if (m_checkpointId == CheckpointControlFile::invalidId) { + if (m_checkpointId == CheckpointControlFile::INVALID_ID) { return 0; // fresh install probably. no error } @@ -174,21 +191,21 @@ int CheckpointRecovery::FillTasksFromMapFile() CheckpointUtils::MakeMapFilename(mapFile, m_workingDir, m_checkpointId); int fd = -1; if (!CheckpointUtils::OpenFileRead(mapFile, fd)) { - MOT_LOG_ERROR("CheckpointRecovery::fillTasksFromMapFile: failed to open map file '%s'", mapFile.c_str()); + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to open map file '%s'", mapFile.c_str()); return -1; } CheckpointUtils::MapFileHeader mapFileHeader; if (CheckpointUtils::ReadFile(fd, (char*)&mapFileHeader, sizeof(CheckpointUtils::MapFileHeader)) != sizeof(CheckpointUtils::MapFileHeader)) { - MOT_LOG_ERROR("CheckpointRecovery::fillTasksFromMapFile: failed to read map file '%s' header", mapFile.c_str()); - CheckpointUtils::CloseFile(fd); + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to read map file '%s' header", mapFile.c_str()); + (void)CheckpointUtils::CloseFile(fd); return -1; } - if (mapFileHeader.m_magic != CP_MGR_MAGIC) { - MOT_LOG_ERROR("CheckpointRecovery::fillTasksFromMapFile: failed to verify map file'%s'", mapFile.c_str()); - CheckpointUtils::CloseFile(fd); + if (mapFileHeader.m_magic != CheckpointUtils::HEADER_MAGIC) { + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to verify map file'%s'", mapFile.c_str()); + (void)CheckpointUtils::CloseFile(fd); return -1; } @@ -196,26 +213,30 @@ int CheckpointRecovery::FillTasksFromMapFile() for (uint64_t i = 0; i < mapFileHeader.m_numEntries; i++) { if (CheckpointUtils::ReadFile(fd, (char*)&entry, sizeof(CheckpointManager::MapFileEntry)) != sizeof(CheckpointManager::MapFileEntry)) { - MOT_LOG_ERROR("CheckpointRecovery::fillTasksFromMapFile: failed to read map file '%s' entry: %lu", + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to read map file '%s' entry: %lu", mapFile.c_str(), i); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return -1; } - m_tableIds.insert(entry.m_tableId); - for (uint32_t i = 0; i <= entry.m_maxSegId; i++) { - Task* recoveryTask = new (std::nothrow) Task(entry.m_tableId, i); + (void)m_tableIds.insert(entry.m_tableId); + for (uint32_t j = 0; j <= entry.m_maxSegId; j++) { + Task* recoveryTask = new (std::nothrow) Task(entry.m_tableId, j); if (recoveryTask == nullptr) { - CheckpointUtils::CloseFile(fd); - MOT_LOG_ERROR("CheckpointRecovery::fillTasksFromMapFile: failed to allocate task object"); + (void)CheckpointUtils::CloseFile(fd); + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to allocate task object"); return -1; } m_tasksList.push_back(recoveryTask); } } - CheckpointUtils::CloseFile(fd); - MOT_LOG_INFO("CheckpointRecovery::fillTasksFromMapFile: filled %lu tasks", m_tasksList.size()); + if (CheckpointUtils::CloseFile(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::FillTasksFromMapFile: Failed to close map file"); + return -1; + } + + MOT_LOG_INFO("CheckpointRecovery::FillTasksFromMapFile: Filled %lu tasks", m_tasksList.size()); return 1; } @@ -229,38 +250,46 @@ bool CheckpointRecovery::RecoverTableMetadata(uint32_t tableId) return false; } - CheckpointUtils::MetaFileHeader mFileHeader; - size_t reader = CheckpointUtils::ReadFile(fd, (char*)&mFileHeader, sizeof(CheckpointUtils::MetaFileHeader)); - if (reader != sizeof(CheckpointUtils::MetaFileHeader)) { + CheckpointUtils::MetaFileHeader mFileHeader = {}; + size_t readSize = + !m_preMvccUpgrade ? sizeof(CheckpointUtils::MetaFileHeader) : sizeof(CheckpointUtils::MetaFileHeaderBase); + size_t reader = CheckpointUtils::ReadFile(fd, (char*)(&mFileHeader), readSize); + if (reader != readSize) { MOT_LOG_ERROR("CheckpointRecovery::recoverTableMetadata: failed to read meta file header, reader %lu", reader); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return false; } - if (mFileHeader.m_fileHeader.m_magic != CP_MGR_MAGIC || mFileHeader.m_fileHeader.m_tableId != tableId) { + if (mFileHeader.m_fileHeader.m_magic != CheckpointUtils::HEADER_MAGIC || + mFileHeader.m_fileHeader.m_tableId != tableId) { MOT_LOG_ERROR("CheckpointRecovery::recoverTableMetadata: file: %s is corrupted", fileName.c_str()); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return false; } - char* dataBuf = new (std::nothrow) char[mFileHeader.m_entryHeader.m_dataLen]; + size_t dataLen = mFileHeader.m_entryHeader.m_base.m_dataLen; + char* dataBuf = new (std::nothrow) char[dataLen]; if (dataBuf == nullptr) { MOT_LOG_ERROR("CheckpointRecovery::recoverTableMetadata: failed to allocate table buffer"); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return false; } - reader = CheckpointUtils::ReadFile(fd, dataBuf, mFileHeader.m_entryHeader.m_dataLen); - if (reader != mFileHeader.m_entryHeader.m_dataLen) { - MOT_LOG_ERROR("CheckpointRecovery::recoverTableMetadata: failed to read table entry (%u), reader %lu", - mFileHeader.m_entryHeader.m_dataLen, - reader); - CheckpointUtils::CloseFile(fd); + reader = CheckpointUtils::ReadFile(fd, dataBuf, dataLen); + if (reader != dataLen) { + MOT_LOG_ERROR( + "CheckpointRecovery::recoverTableMetadata: failed to read table entry (%u), reader %lu", dataLen, reader); + (void)CheckpointUtils::CloseFile(fd); + delete[] dataBuf; + return false; + } + + if (CheckpointUtils::CloseFile(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::recoverTableMetadata: failed to close file: %s", fileName.c_str()); delete[] dataBuf; return false; } - CheckpointUtils::CloseFile(fd); bool status = CreateTable(dataBuf); delete[] dataBuf; return status; @@ -322,68 +351,101 @@ bool CheckpointRecovery::CreateTable(char* data) void CheckpointRecovery::OnError(RC errCode, const char* errMsg, const char* optionalMsg) { - m_errorLock.lock(); + std::lock_guard lock(m_errorLock); m_stopWorkers = true; if (!m_errorSet) { m_errorCode = errCode; m_errorMessage.clear(); - m_errorMessage.append(errMsg); + (void)m_errorMessage.append(errMsg); if (optionalMsg != nullptr) { - m_errorMessage.append(" "); - m_errorMessage.append(optionalMsg); + (void)m_errorMessage.append(" "); + (void)m_errorMessage.append(optionalMsg); } m_errorSet = true; } - m_errorLock.unlock(); } -void CheckpointRecovery::CheckpointRecoveryWorker(CheckpointRecovery* checkpointRecovery) +void CheckpointRecovery::CheckpointRecoveryWorker(uint32_t workerId, CheckpointRecovery* checkpointRecovery) { // since this is a non-kernel thread we must set-up our own u_sess struct for the current thread MOT_DECLARE_NON_KERNEL_THREAD(); + char threadName[ThreadContext::THREAD_NAME_LEN]; + errno_t rc = snprintf_s(threadName, + ThreadContext::THREAD_NAME_LEN, + ThreadContext::THREAD_NAME_LEN - 1, + "%s%u", + CKPT_RECOVERY_WORKER_NAME, + workerId); + securec_check_ss(rc, "", ""); + + (void)pthread_setname_np(pthread_self(), threadName); + + MOT_LOG_INFO("%s - Starting", threadName); + MOT::MOTEngine* engine = MOT::MOTEngine::GetInstance(); + uint16_t threadId = MOTCurrThreadId; SessionContext* sessionContext = GetSessionManager()->CreateSessionContext(); - int threadId = MOTCurrThreadId; + if (sessionContext == nullptr) { + MOT_LOG_ERROR("CheckpointRecoveryWorker: Failed to initialize Session Context"); + checkpointRecovery->OnError( + RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecoveryWorker: Failed to initialize Session Context"); + engine->OnCurrentThreadEnding(); + MOT_LOG_DEBUG("%s - Exiting", threadName); + return; + } // in a thread-pooled envelope the affinity could be disabled, so we use task affinity here if (GetGlobalConfiguration().m_enableNuma && !GetTaskAffinity().SetAffinity(threadId)) { - MOT_LOG_WARN("Failed to set affinity of checkpoint recovery worker, recovery from checkpoint performance may be" - " affected"); + MOT_LOG_WARN("Failed to set affinity of checkpoint recovery worker, recovery from checkpoint performance " + "may be affected"); } SurrogateState sState; - if (sState.IsValid() == false) { + if (!sState.Init()) { checkpointRecovery->OnError( - RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecovery::WorkerFunc failed to allocate surrogate state"); + RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecoveryWorker: Failed to allocate surrogate state"); + GetSessionManager()->DestroySessionContext(sessionContext); + engine->OnCurrentThreadEnding(); + MOT_LOG_DEBUG("%s - Exiting", threadName); return; } - MOT_LOG_DEBUG("CheckpointRecovery::WorkerFunc start [%u] on cpu %lu", (unsigned)MOTCurrThreadId, sched_getcpu()); - uint64_t maxCsn = 0; char* keyData = (char*)malloc(MAX_KEY_SIZE); if (keyData == nullptr) { - MOT_LOG_ERROR("RecoveryManager::WorkerFunc: failed to allocate key buffer"); + MOT_LOG_ERROR("CheckpointRecoveryWorker: Failed to allocate key buffer"); checkpointRecovery->OnError( - RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecovery::WorkerFunc failed to allocate key data"); + RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecoveryWorker: Failed to allocate key data"); + GetSessionManager()->DestroySessionContext(sessionContext); + engine->OnCurrentThreadEnding(); + MOT_LOG_DEBUG("%s - Exiting", threadName); + return; } char* entryData = (char*)malloc(MAX_TUPLE_SIZE); if (entryData == nullptr) { - MOT_LOG_ERROR("CheckpointRecovery::WorkerFunc: failed to allocate row buffer"); + MOT_LOG_ERROR("CheckpointRecoveryWorker: Failed to allocate row buffer"); checkpointRecovery->OnError( - RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecovery::WorkerFunc failed to allocate row buffer"); + RC_MEMORY_ALLOCATION_ERROR, "CheckpointRecoveryWorker: Failed to allocate row buffer"); + free(keyData); + GetSessionManager()->DestroySessionContext(sessionContext); + engine->OnCurrentThreadEnding(); + MOT_LOG_DEBUG("%s - Exiting", threadName); + return; } + MOT_LOG_DEBUG("%s[%u] start on cpu %d", threadName, (unsigned)threadId, sched_getcpu()); + + uint64_t maxCsn = 0; RC status = RC_OK; - while (checkpointRecovery->ShouldStopWorkers() == false) { + while (!checkpointRecovery->ShouldStopWorkers()) { CheckpointRecovery::Task* task = checkpointRecovery->GetTask(); if (task != nullptr) { bool hadError = false; if (!checkpointRecovery->RecoverTableRows(task, keyData, entryData, maxCsn, sState, status)) { - MOT_LOG_ERROR("CheckpointRecovery::WorkerFunc recovery of table %lu's data failed", task->m_tableId); + MOT_LOG_ERROR("CheckpointRecoveryWorker: Failed to recover table %u", task->m_tableId); checkpointRecovery->OnError(status, - "CheckpointRecovery::WorkerFunc failed to recover table: ", + "CheckpointRecoveryWorker: Failed to recover table", std::to_string(task->m_tableId).c_str()); hadError = true; } @@ -403,14 +465,14 @@ void CheckpointRecovery::CheckpointRecoveryWorker(CheckpointRecovery* checkpoint free(keyData); } - (GetRecoveryManager())->SetCsn(maxCsn); - if (sState.IsEmpty() == false) { - (GetRecoveryManager())->AddSurrogateArrayToList(sState); + checkpointRecovery->SetMaxCsn(maxCsn); + if (!sState.IsEmpty()) { + GetRecoveryManager()->AddSurrogateArrayToList(sState); } GetSessionManager()->DestroySessionContext(sessionContext); engine->OnCurrentThreadEnding(); - MOT_LOG_DEBUG("CheckpointRecovery::WorkerFunc end [%u] on cpu %lu", (unsigned)MOTCurrThreadId, sched_getcpu()); + MOT_LOG_DEBUG("%s[%u] end on cpu %d", threadName, (unsigned)threadId, sched_getcpu()); } bool CheckpointRecovery::RecoverTableRows( @@ -443,13 +505,13 @@ bool CheckpointRecovery::RecoverTableRows( size_t reader = CheckpointUtils::ReadFile(fd, (char*)&fileHeader, sizeof(CheckpointUtils::FileHeader)); if (reader != sizeof(CheckpointUtils::FileHeader)) { MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: failed to read file header, reader %lu", reader); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return false; } - if (fileHeader.m_magic != CP_MGR_MAGIC || fileHeader.m_tableId != tableId) { + if (fileHeader.m_magic != CheckpointUtils::HEADER_MAGIC || fileHeader.m_tableId != tableId) { MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: file: %s is corrupted", fileName.c_str()); - CheckpointUtils::CloseFile(fd); + (void)CheckpointUtils::CloseFile(fd); return false; } @@ -457,82 +519,66 @@ bool CheckpointRecovery::RecoverTableRows( if (tableExId != fileHeader.m_exId) { MOT_LOG_ERROR( "CheckpointRecovery::RecoverTableRows: exId mismatch: my %lu - pkt %lu", tableExId, fileHeader.m_exId); + (void)CheckpointUtils::CloseFile(fd); return false; } if (IsMemoryLimitReached(m_numWorkers, GetGlobalConfiguration().m_checkpointSegThreshold)) { MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: Memory hard limit reached. Cannot recover datanode"); + (void)CheckpointUtils::CloseFile(fd); return false; } CheckpointUtils::EntryHeader entry; + size_t entryHeaderSize = + !m_preMvccUpgrade ? sizeof(CheckpointUtils::EntryHeader) : sizeof(CheckpointUtils::EntryHeaderBase); for (uint64_t i = 0; i < fileHeader.m_numOps; i++) { - reader = CheckpointUtils::ReadFile(fd, (char*)&entry, sizeof(CheckpointUtils::EntryHeader)); - if (reader != sizeof(CheckpointUtils::EntryHeader)) { - MOT_LOG_ERROR( - "CheckpointRecovery::RecoverTableRows: failed to read entry header (elem: %lu / %lu), reader %lu", + status = ReadEntry(fd, entryHeaderSize, entry, keyData, entryData); + if (status != RC_OK) { + MOT_LOG_ERROR("CheckpointRecovery: failed to read row (elem: %lu / %lu), error: %s (%d)", i, fileHeader.m_numOps, - reader); - status = RC_ERROR; - break; - } - - if (entry.m_keyLen > MAX_KEY_SIZE || entry.m_dataLen > MAX_TUPLE_SIZE) { - MOT_LOG_ERROR( - "CheckpointRecovery::RecoverTableRows: invalid entry (elem: %lu / %lu), keyLen %u, dataLen %u", - i, - fileHeader.m_numOps, - entry.m_keyLen, - entry.m_dataLen); - status = RC_ERROR; - break; - } - - reader = CheckpointUtils::ReadFile(fd, keyData, entry.m_keyLen); - if (reader != entry.m_keyLen) { - MOT_LOG_ERROR( - "CheckpointRecovery::RecoverTableRows: failed to read entry key (elem: %lu / %lu), reader %lu", - i, - fileHeader.m_numOps, - reader); - status = RC_ERROR; - break; - } - - reader = CheckpointUtils::ReadFile(fd, entryData, entry.m_dataLen); - if (reader != entry.m_dataLen) { - MOT_LOG_ERROR( - "CheckpointRecovery::RecoverTableRows: failed to read entry data (elem: %lu / %lu), reader %lu", - i, - fileHeader.m_numOps, - reader); - status = RC_ERROR; + RcToString(status), + (int)status); break; } InsertRow(table, keyData, - entry.m_keyLen, + entry.m_base.m_keyLen, entryData, - entry.m_dataLen, - entry.m_csn, + entry.m_base.m_dataLen, + entry.m_base.m_csn, MOTCurrThreadId, sState, status, - entry.m_rowId); - + entry.m_base.m_rowId, + entry.m_transactionId); if (status != RC_OK) { - MOT_LOG_ERROR( - "CheckpointRecovery: failed to insert row %s (error code: %d)", RcToString(status), (int)status); + MOT_LOG_ERROR("CheckpointRecovery: failed to insert row (elem: %lu / %lu), error: %s (%d)", + i, + fileHeader.m_numOps, + RcToString(status), + (int)status); break; } - MOT_LOG_DEBUG("Inserted into table %u row with CSN %" PRIu64, tableId, entry.m_csn); - if (entry.m_csn > maxCsn) - maxCsn = entry.m_csn; - } - CheckpointUtils::CloseFile(fd); + MOT_LOG_DEBUG("Inserted into table %u row with CSN %" PRIu64, tableId, entry.m_base.m_csn); + if (entry.m_base.m_csn > maxCsn) { + maxCsn = entry.m_base.m_csn; + } + } + + if (CheckpointUtils::CloseFile(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: failed to close file: %s", fileName.c_str()); + if (status == RC_OK) { + status = RC_ERROR; + } + } + + if (status == RC_OK) { + table->UpdateRowCount(fileHeader.m_numOps); + } MOT_LOG_DEBUG("[%u] CheckpointRecovery::RecoverTableRows table %u:%u, %lu rows recovered (%s)", MOTCurrThreadId, tableId, @@ -542,10 +588,50 @@ bool CheckpointRecovery::RecoverTableRows( return (status == RC_OK); } +RC CheckpointRecovery::ReadEntry( + int fd, size_t entryHeaderSize, CheckpointUtils::EntryHeader& entry, char* keyData, char* entryData) const +{ + size_t reader = CheckpointUtils::ReadFile(fd, (char*)&entry, entryHeaderSize); + if (reader != entryHeaderSize) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: failed to read entry header, reader %lu", reader); + return RC_ERROR; + } + + if (m_preMvccUpgrade) { + // Set a default transaction id in case of upgrade from 1VCC to MVCC. + entry.m_transactionId = INVALID_TRANSACTION_ID; + + // In case of upgrade from 1VCC to MVCC, we use INITIAL_CSN, because the CSN in the 1VCC entry header + // is not compatible with envelope CSN. + entry.m_base.m_csn = INITIAL_CSN; + } + + if (entry.m_base.m_keyLen > MAX_KEY_SIZE || entry.m_base.m_dataLen > MAX_TUPLE_SIZE) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: invalid entry, keyLen %u, dataLen %u", + entry.m_base.m_keyLen, + entry.m_base.m_dataLen); + return RC_ERROR; + } + + reader = CheckpointUtils::ReadFile(fd, keyData, entry.m_base.m_keyLen); + if (reader != entry.m_base.m_keyLen) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: failed to read entry key, reader %lu", reader); + return RC_ERROR; + } + + reader = CheckpointUtils::ReadFile(fd, entryData, entry.m_base.m_dataLen); + if (reader != entry.m_base.m_dataLen) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverTableRows: failed to read entry data, reader %lu", reader); + return RC_ERROR; + } + + return RC_OK; +} + CheckpointRecovery::Task* CheckpointRecovery::GetTask() { + std::lock_guard lock(m_tasksLock); Task* task = nullptr; - m_tasksLock.lock(); do { if (m_tasksList.empty()) { break; @@ -553,19 +639,16 @@ CheckpointRecovery::Task* CheckpointRecovery::GetTask() task = m_tasksList.front(); m_tasksList.pop_front(); } while (0); - m_tasksLock.unlock(); return task; } -uint32_t CheckpointRecovery::HaveTasks() +bool CheckpointRecovery::AllTasksDone() { - m_tasksLock.lock(); - bool noMoreTasks = m_tasksList.empty(); - m_tasksLock.unlock(); - return !noMoreTasks; + std::lock_guard lock(m_tasksLock); + return m_tasksList.empty(); } -bool CheckpointRecovery::IsMemoryLimitReached(uint32_t numThreads, uint32_t neededBytes) +bool CheckpointRecovery::IsMemoryLimitReached(uint32_t numThreads, uint64_t neededBytes) const { uint64_t memoryRequiredBytes = (uint64_t)numThreads * neededBytes; if (MOTEngine::GetInstance()->GetCurrentMemoryConsumptionBytes() + memoryRequiredBytes >= @@ -582,7 +665,7 @@ bool CheckpointRecovery::IsMemoryLimitReached(uint32_t numThreads, uint32_t need } void CheckpointRecovery::InsertRow(Table* table, char* keyData, uint16_t keyLen, char* rowData, uint64_t rowLen, - uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId) + uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId, uint64_t version) { MaxKey key; Row* row = table->CreateNewRow(); @@ -597,18 +680,25 @@ void CheckpointRecovery::InsertRow(Table* table, char* keyData, uint16_t keyLen, MOT::Index* ix = table->GetPrimaryIndex(); if (ix->IsFakePrimary()) { - row->SetSurrogateKey(*(uint64_t*)keyData); - sState.UpdateMaxKey(rowId); + row->SetSurrogateKey(); + if (!sState.UpdateMaxKey(rowId)) { + status = RC_ERROR; + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Recovery Manager Insert Row", "Failed to update surrogate state"); + table->DestroyRow(row); + return; + } } key.CpKey((const uint8_t*)keyData, keyLen); status = table->InsertRowNonTransactional(row, tid, &key); if (status != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Insert Row", "failed to insert row"); table->DestroyRow(row); + } else { + row->GetPrimarySentinel()->SetTransactionId(version); } } -bool CheckpointRecovery::RecoverInProcessTxns() +bool CheckpointRecovery::RecoverInProcessData() { int fd = -1; std::string fileName; @@ -619,137 +709,63 @@ bool CheckpointRecovery::RecoverInProcessTxns() break; } - CheckpointUtils::MakeTpcFilename(fileName, workingDir, m_checkpointId); - if (!CheckpointUtils::IsFileExists(fileName)) { - MOT_LOG_INFO("RecoveryManager::RecoverInProcessTxns: tpc file does not exist"); - ret = true; - break; + if (CheckpointControlFile::GetCtrlFile()->GetMetaVersion() < METADATA_VER_LOW_RTO) { + CheckpointUtils::MakeTpcFilename(fileName, workingDir, m_checkpointId); + if (!CheckpointUtils::IsFileExists(fileName)) { + // In previous versions, Tpc file won't be created if there are no in-process txn data. + ret = true; + break; + } + } else { + CheckpointUtils::MakeIpdFilename(fileName, workingDir, m_checkpointId); } if (!CheckpointUtils::OpenFileRead(fileName, fd)) { - MOT_LOG_ERROR("RecoveryManager::RecoverInProcessTxns: failed to open file '%s'", fileName.c_str()); + MOT_LOG_ERROR("CheckpointRecovery::RecoverInProcessData: failed to open file '%s'", fileName.c_str()); break; } - CheckpointUtils::TpcFileHeader tpcFileHeader; - if (CheckpointUtils::ReadFile(fd, (char*)&tpcFileHeader, sizeof(CheckpointUtils::TpcFileHeader)) != - sizeof(CheckpointUtils::TpcFileHeader)) { - MOT_LOG_ERROR("RecoveryManager::RecoverInProcessTxns: failed to read file '%s' header", fileName.c_str()); - CheckpointUtils::CloseFile(fd); - break; - } - - if (tpcFileHeader.m_magic != CP_MGR_MAGIC) { + CheckpointUtils::PendingTxnDataFileHeader ptdFileHeader; + if (CheckpointUtils::ReadFile(fd, (char*)&ptdFileHeader, sizeof(CheckpointUtils::PendingTxnDataFileHeader)) != + sizeof(CheckpointUtils::PendingTxnDataFileHeader)) { MOT_LOG_ERROR( - "RecoveryManager::RecoverInProcessTxns: failed to validate file's header ('%s')", fileName.c_str()); - CheckpointUtils::CloseFile(fd); + "CheckpointRecovery::RecoverInProcessData: failed to read file '%s' header", fileName.c_str()); + (void)CheckpointUtils::CloseFile(fd); break; } - if (tpcFileHeader.m_numEntries == 0) { - MOT_LOG_INFO("RecoveryManager::RecoverInProcessTxns: no tpc entries to recover"); - CheckpointUtils::CloseFile(fd); + if (ptdFileHeader.m_magic != CheckpointUtils::HEADER_MAGIC) { + MOT_LOG_ERROR( + "CheckpointRecovery::RecoverInProcessData: failed to validate file's header ('%s')", fileName.c_str()); + (void)CheckpointUtils::CloseFile(fd); + break; + } + + if (ptdFileHeader.m_numEntries == 0) { + MOT_LOG_INFO("RecoveryManager::RecoverInProcessData: no pending data to recover"); + if (CheckpointUtils::CloseFile(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverInProcessData: failed to close file '%s'", fileName.c_str()); + break; + } ret = true; break; } - if (!DeserializeInProcessTxns(fd, tpcFileHeader.m_numEntries)) { - MOT_LOG_ERROR("RecoveryManager::RecoverInProcessTxns: failed to deserialize in-process transactions"); - CheckpointUtils::CloseFile(fd); + if (!GetRecoveryManager()->DeserializePendingRecoveryData(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverInProcessData: deserialization failed"); + (void)CheckpointUtils::CloseFile(fd); break; } - CheckpointUtils::CloseFile(fd); + if (CheckpointUtils::CloseFile(fd)) { + MOT_LOG_ERROR("CheckpointRecovery::RecoverInProcessData: failed to close file '%s'", fileName.c_str()); + break; + } ret = true; } while (0); return ret; } -bool CheckpointRecovery::DeserializeInProcessTxns(int fd, uint64_t numEntries) -{ - errno_t erc; - char* buf = nullptr; - size_t bufSize = 0; - uint32_t readEntries = 0; - bool success = false; - - CheckpointUtils::TpcEntryHeader header; - if (fd == -1) { - MOT_LOG_ERROR("DeserializeInProcessTxns: bad fd"); - return false; - } - - while (readEntries < numEntries) { - success = false; - size_t sz = CheckpointUtils::ReadFile(fd, (char*)&header, sizeof(CheckpointUtils::TpcEntryHeader)); - if (sz == 0) { - MOT_LOG_DEBUG("DeserializeInProcessTxns: eof, read %d entries", readEntries); - success = true; - break; - } else if (sz != sizeof(CheckpointUtils::TpcEntryHeader)) { - MOT_LOG_ERROR("DeserializeInProcessTxns: failed to read segment header", sz, errno, gs_strerror(errno)); - break; - } - - if (header.m_magic != CP_MGR_MAGIC || header.m_len > RedoLogBuffer::REDO_DEFAULT_BUFFER_SIZE) { - MOT_LOG_ERROR("DeserializeInProcessTxns: bad entry %lu - %lu", header.m_magic, header.m_len); - break; - } - - MOT_LOG_DEBUG("DeserializeInProcessTxns: entry len %lu", header.m_len); - if (buf == nullptr || header.m_len > bufSize) { - if (buf != nullptr) { - free(buf); - } - buf = (char*)malloc(header.m_len); - MOT_LOG_DEBUG("DeserializeInProcessTxns: alloc %lu - %p", header.m_len, buf); - bufSize = header.m_len; - } - - if (buf == nullptr) { - MOT_LOG_ERROR("DeserializeInProcessTxns: failed to allocate buffer (%lu bytes)", header.m_magic); - break; - } - - if (CheckpointUtils::ReadFile(fd, buf, bufSize) != bufSize) { - MOT_LOG_ERROR("DeserializeInProcessTxns: failed to read data from file (%lu bytes)", bufSize); - break; - } - - MOT::LogSegment* segment = new (std::nothrow) MOT::LogSegment(); - if (segment == nullptr) { - MOT_LOG_ERROR("DeserializeInProcessTxns: failed to allocate segment"); - break; - } - - segment->m_replayLsn = 0; - erc = memcpy_s(&segment->m_len, sizeof(size_t), buf, sizeof(size_t)); - securec_check(erc, "\0", "\0"); - segment->m_data = new (std::nothrow) char[segment->m_len]; - if (segment->m_data == nullptr) { - MOT_LOG_ERROR("DeserializeInProcessTxns: failed to allocate memory for segment data"); - delete segment; - break; - } - - segment->Deserialize(buf); - if (!MOTEngine::GetInstance()->GetInProcessTransactions().InsertLogSegment(segment)) { - MOT_LOG_ERROR("DeserializeInProcessTxns: insert log segment failed"); - delete segment; - break; - } - readEntries++; - success = true; - } - - if (buf != nullptr) { - free(buf); - } - - MOT_LOG_INFO("DeserializeInProcessTxns: processed %u entries", readEntries); - return success; -} - bool CheckpointRecovery::IsCheckpointValid(uint64_t id) { std::string fileName; diff --git a/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.h b/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.h index b1ba8c0dc..5911c6a0a 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.h +++ b/src/gausskernel/storage/mot/core/system/recovery/checkpoint_recovery.h @@ -32,6 +32,7 @@ #include "spin_lock.h" #include "table.h" #include "surrogate_state.h" +#include "checkpoint_utils.h" namespace MOT { class CheckpointRecovery { @@ -40,10 +41,13 @@ public: : m_checkpointId(0), m_lsn(0), m_lastReplayLsn(0), + m_maxCsn(INVALID_CSN), + m_maxTransactionId(INVALID_TRANSACTION_ID), m_numWorkers(GetGlobalConfiguration().m_checkpointRecoveryWorkers), m_stopWorkers(false), m_errorSet(false), - m_errorCode(RC_OK) + m_errorCode(RC_OK), + m_preMvccUpgrade(false) {} ~CheckpointRecovery() @@ -108,7 +112,35 @@ public: * @brief Implements the a checkpoint recovery worker * @param checkpointRecovery The caller checkpoint recovery class */ - static void CheckpointRecoveryWorker(CheckpointRecovery* checkpointRecovery); + static void CheckpointRecoveryWorker(uint32_t workerId, CheckpointRecovery* checkpointRecovery); + + inline void SetMaxCsn(uint64_t newMaxCsn) + { + uint64_t currentMaxCsn = INVALID_CSN; + do { + currentMaxCsn = m_maxCsn; + if (currentMaxCsn >= newMaxCsn) { + break; + } + } while (!m_maxCsn.compare_exchange_strong(currentMaxCsn, newMaxCsn, std::memory_order_acq_rel)); + } + + inline uint64_t GetMaxCsn() const + { + return m_maxCsn; + } + + inline uint64_t GetMaxTransactionId() const + { + return m_maxTransactionId; + } + + inline void SetLastReplayLsn(uint64_t replayLsn) + { + if (m_lastReplayLsn < replayLsn) { + m_lastReplayLsn = replayLsn; + } + } private: /** @@ -128,27 +160,17 @@ private: int FillTasksFromMapFile(); /** - * @brief Checks if there are any more tasks left in the queue - * @return Int value where 0 means failure and 1 success + * @brief Checks if all the tasks are completed. + * @return Boolean value that is true if all the tasks are completed. */ - uint32_t HaveTasks(); + bool AllTasksDone(); + /** + * @brief Spawns threads to perform the recovery from checkpoint and waits for them to complete. + * @return Boolean value that is true if the recovery was successfully completed. + */ bool PerformRecovery(); - /** - * @brief Recovers the in process two phase commit related transactions - * from the checkpoint data file. - * @return Boolean value denoting success or failure. - */ - bool RecoverInProcessTxns(); - - /** - * @brief Deserializes the in process two phase commit data from the - * checkpoint data file. called by RecoverTpc. - * @return Boolean value denoting success or failure. - */ - bool DeserializeInProcessTxns(int fd, uint64_t numEntries); - /** * @brief Inserts a row into the database in a non transactional manner. * @param table the table's object pointer. @@ -159,11 +181,12 @@ private: * @param csn the operation's csn. * @param tid the thread id of the recovering thread. * @param sState the returned surrogate state. - * @param status the returned status of the operation - * @param rowId the row's internal id + * @param status the returned status of the operation. + * @param rowId the row's internal id. + * @param version the row's version. */ void InsertRow(Table* table, char* keyData, uint16_t keyLen, char* rowData, uint64_t rowLen, uint64_t csn, - uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId); + uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId, uint64_t version); /** * @brief performs table creation. @@ -186,7 +209,16 @@ private: * @return Boolean value that is true if there is not enough memory for * recovery. */ - bool IsMemoryLimitReached(uint32_t numThreads, uint32_t neededMBs); + bool IsMemoryLimitReached(uint32_t numThreads, uint64_t neededBytes) const; + + /** + * @brief Recovers in process transaction data. + * @return Boolean value that represents that status of the operation. + */ + bool RecoverInProcessData(); + + RC ReadEntry( + int fd, size_t entryHeaderSize, CheckpointUtils::EntryHeader& entry, char* keyData, char* entryData) const; uint64_t m_checkpointId; @@ -194,6 +226,10 @@ private: uint64_t m_lastReplayLsn; + std::atomic m_maxCsn; + + uint64_t m_maxTransactionId; + uint32_t m_numWorkers; std::string m_workingDir; @@ -213,6 +249,8 @@ private: std::set m_tableIds; std::list m_tasksList; + + bool m_preMvccUpgrade; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.cpp b/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.cpp deleted file mode 100644 index aff8ec1be..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * inprocess_transactions.cpp - * Implements a map that holds transactions which are pending commit or abort. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.cpp - * - * ------------------------------------------------------------------------- - */ - -#include "inprocess_transactions.h" - -namespace MOT { -DECLARE_LOGGER(RecoveryManager, InProcessTransactions); - -InProcessTransactions::~InProcessTransactions() -{ - Clear(); -} - -bool InProcessTransactions::InsertLogSegment(LogSegment* segment) -{ - uint64_t transactionId = segment->m_controlBlock.m_internalTransactionId; - RedoLogTransactionSegments* transactionLogEntries = nullptr; - - const std::lock_guard lock(m_lock); - auto it = m_map.find(transactionId); - if (it == m_map.end()) { - // this is a new transaction. Not found in the map. - transactionLogEntries = new (std::nothrow) RedoLogTransactionSegments(transactionId); - if (transactionLogEntries == nullptr) { - return false; - } - if (!transactionLogEntries->Append(segment)) { - MOT_LOG_ERROR("InsertLogSegment: could not append log segment, error re-allocating log segments array"); - return false; - } - m_map[transactionId] = transactionLogEntries; - m_numEntries++; - } else { - transactionLogEntries = it->second; - if (!transactionLogEntries->Append(segment)) { - MOT_LOG_ERROR("InsertLogSegment: could not append log segment, error re-allocating log segments array"); - return false; - } - } - - if (segment->m_controlBlock.m_externalTransactionId != INVALID_TRANSACTION_ID) { - m_extToInt[segment->m_controlBlock.m_externalTransactionId] = segment->m_controlBlock.m_internalTransactionId; - } - if (segment->m_replayLsn > m_replayLsn) { - m_replayLsn = segment->m_replayLsn; - } - return true; -} - -bool InProcessTransactions::FindTransactionId(uint64_t externalId, uint64_t& internalId) -{ - internalId = 0; - const std::lock_guard lock(m_lock); - auto it = m_extToInt.find(externalId); - if (it != m_extToInt.end()) { - internalId = it->second; - return true; - } - return false; -} -} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.h b/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.h deleted file mode 100644 index 545ebe690..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * inprocess_transactions.h - * Implements a map that holds transactions which are pending commit or abort. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/inprocess_transactions.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef INPROCESS_TRANSACTIONS_H -#define INPROCESS_TRANSACTIONS_H - -#include -#include -#include -#include "redo_log_transaction_segments.h" - -namespace MOT { -class InProcessTransactions { -public: - InProcessTransactions() : m_numEntries(0), m_replayLsn(0) - {} - - ~InProcessTransactions(); - - bool InsertLogSegment(LogSegment* segment); - - bool FindTransactionId(uint64_t externalId, uint64_t& internalId); - - template - RC ForUniqueTransaction(uint64_t internalId, uint64_t externalId, const T& func) - { - const std::lock_guard lock(m_lock); - auto it = m_map.find(internalId); - if (it != m_map.end()) { - RedoLogTransactionSegments* segments = it->second; - RC status = func(segments, it->first); - m_map.erase(it); - m_extToInt.erase(externalId); - m_numEntries--; - delete segments; - return status; - } - return RC_ERROR; - } - - /* Attention: Caller's should acquire the lock by calling Lock() method, before calling this method. */ - template - RC ForEachTransactionNoLock(const T& func) - { - auto it = m_map.begin(); - while (it != m_map.end()) { - RedoLogTransactionSegments* segments = it->second; - RC status = func(segments, it->first); - if (status != RC_OK) { - return status; - } - ++it; - } - return RC_OK; - } - - void Lock() - { - m_lock.lock(); - } - - void Unlock() - { - m_lock.unlock(); - } - - uint64_t GetNumTxns() const - { - return m_numEntries; - } - - uint64_t GetReplayLsn() const - { - return m_replayLsn; - } - - void Clear() - { - const std::lock_guard lock(m_lock); - if (m_numEntries > 0) { - auto it = m_map.begin(); - while (it != m_map.end()) { - RedoLogTransactionSegments* segments = it->second; - delete segments; - ++it; - } - m_map.clear(); - m_extToInt.clear(); - m_numEntries = 0; - } - } - -private: - std::mutex m_lock; - - std::map m_map; - - std::map m_extToInt; - - volatile uint64_t m_numEntries; - - uint64_t m_replayLsn; -}; -} // namespace MOT - -#endif /* INPROCESS_TRANSACTIONS_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/irecovery_manager.h b/src/gausskernel/storage/mot/core/system/recovery/irecovery_manager.h index 9ae6d52fc..287a21d1b 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/irecovery_manager.h +++ b/src/gausskernel/storage/mot/core/system/recovery/irecovery_manager.h @@ -29,7 +29,9 @@ #include "surrogate_state.h" namespace MOT { + typedef TxnCommitStatus (*CommitLogStatusCallback)(uint64_t); +class RedoLogTransactionPlayer; class IRecoveryManager { public: @@ -41,11 +43,6 @@ public: virtual void SetCommitLogCallback(CommitLogStatusCallback clogCallback) = 0; - /** - * @brief Cleans up the recovery object. - */ - virtual void CleanUp() = 0; - /** * @brief Starts the recovery process which currently consists of * checkpoint recovery. @@ -70,24 +67,103 @@ public: virtual bool ApplyRedoLog(uint64_t redoLsn, char* data, size_t len) = 0; /** - * @brief attempts to insert a data chunk into the in-process - * transactions map and operate on it - * @return Boolean value denoting success or failure. - */ - virtual bool ApplyLogSegmentFromData(char* data, size_t len, uint64_t replayLsn = 0) = 0; - - /** - * @brief performs a commit on an in-process transaction, + * @brief performs a commit on an in-process transaction. + * @param extTxnId the transaction id. * @return Boolean value denoting success or failure to commit. */ - virtual bool CommitRecoveredTransaction(uint64_t externalTransactionId) = 0; + virtual bool CommitTransaction(uint64_t extTxnId) = 0; + /** + * @brief Sets the last replayed LSN value. + * @param uint64_t the lsn value. + */ virtual void SetLastReplayLsn(uint64_t lastReplayLsn) = 0; + + /** + * @brief Returns the last replayed LSN value. + * @return uint64 the lsn value. + */ virtual uint64_t GetLastReplayLsn() const = 0; + /** + * @brief Sets the flag to indicate an error in recovery. + */ + virtual void SetError() = 0; + + /** + * @brief Checks for an error in recovery. + * @return Boolean value denoting true in case of an error. + */ virtual bool IsErrorSet() const = 0; + + /** + * @brief Adds a surrogate array into the surrogate global list. + * @param SurrogateState the surrogate state object from which to obtain the array. + */ virtual void AddSurrogateArrayToList(SurrogateState& surrogate) = 0; - virtual void SetCsn(uint64_t csn) = 0; + + /** + * @brief Sets the max recovery csn. + * @param uint64 the csn value to set (if greater than the current). + */ + virtual void SetMaxCsn(uint64_t csn) = 0; + + /** + * @brief Returns the number of currently running recovery threads. + * @return uint32 the number of threads. + */ + virtual uint32_t GetNumRecoveryThreads() const = 0; + + /** + * @brief Instructs the manager to flush its thread queues (if any) in case a checkpoint needs + * to be performed. + */ + virtual void Flush() = 0; + + /** + * @brief Returns whether an operation should be retried in case of failure. + * @param RedoLogTransactionPlayer* the player currently need to be retried. + * @return bool whether to retry the operation or not. + */ + virtual bool ShouldRetryOp(RedoLogTransactionPlayer* player) = 0; + + /** + * @brief Releases the player after the transaction is committed. + * @param RedoLogTransactionPlayer* the player to be released. + */ + virtual void ReleasePlayer(RedoLogTransactionPlayer* player) = 0; + + /** + * @brief Will be called when a checkpoint begins. used to serialize pending transactions, etc. + * @param fd the file descriptor to use. + * @return uint64_t the num of transactions that were processed. + */ + virtual uint64_t SerializePendingRecoveryData(int fd) = 0; + + /** + * @brief Will be called during checkpoint recovery to restore the state of the pending transactions. + * @param fd the file descriptor to use. + * @return Boolean value denoting success or failure. + */ + virtual bool DeserializePendingRecoveryData(int fd) = 0; + + /** + * @brief locks the recovery manager in case of transaction serialization needs to be performed. + * + */ + virtual void Lock() = 0; + + /** + * @brief unlocks the recovery manager when serialization is done. + * + */ + virtual void Unlock() = 0; + + /* Stats Collector API */ + virtual void LogInsert(uint64_t) = 0; + virtual void LogUpdate(uint64_t) = 0; + virtual void LogDelete(uint64_t) = 0; + virtual void LogCommit() = 0; protected: // constructor diff --git a/src/gausskernel/storage/mot/core/system/recovery/irecovery_ops_context.h b/src/gausskernel/storage/mot/core/system/recovery/irecovery_ops_context.h new file mode 100644 index 000000000..e9d2b60b8 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/irecovery_ops_context.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * irecovery_ops_context.h + * Recovery Ops Context. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/irecovery_ops_context.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef IRECOVERY_OPS_CONTEXT_H +#define IRECOVERY_OPS_CONTEXT_H + +#include "global.h" +#include "txn.h" + +namespace MOT { + +class IRecoveryOpsContext { +public: + virtual TxnManager* GetTxn() = 0; + virtual bool ShouldRetryOp() = 0; + virtual ~IRecoveryOpsContext() + {} +}; + +} // namespace MOT + +#endif /* IRECOVERY_OPS_CONTEXT_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/log_segment.h b/src/gausskernel/storage/mot/core/system/recovery/log_segment.h index 8a6f9eaf9..60b8d7de6 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/log_segment.h +++ b/src/gausskernel/storage/mot/core/system/recovery/log_segment.h @@ -28,8 +28,11 @@ #include "redo_log_global.h" #include "redo_log_writer.h" #include "serializable.h" +#include "spsc_allocator.h" namespace MOT { +class RedoLogTransactionPlayer; + /** * @struct LogSegment * @brief encapsulates a chunk of logging data @@ -43,20 +46,36 @@ struct LogSegment : public Serializable { uint64_t m_replayLsn; + SPSCVarSizeAllocator* m_allocator; + + RedoLogTransactionPlayer* m_player; + + LogSegment() : m_data(nullptr), m_len(0), m_replayLsn(0), m_allocator(nullptr), m_player(nullptr) + {} + ~LogSegment() { if (m_data != nullptr) { - delete[] m_data; + if (m_allocator == nullptr) { + delete[] m_data; + } else { + m_allocator->Release(m_data); + m_allocator = nullptr; + } + m_data = nullptr; } + m_player = nullptr; + m_allocator = nullptr; } - /** - * @brief checks if this log segment is part of a two-phase transaction - * @return Boolean value denoting if it is part of a two-phase transaction or not. - */ - bool IsTwoPhase() + inline void SetPlayer(RedoLogTransactionPlayer* player) { - return (m_controlBlock.m_opCode == PREPARE_TX || m_controlBlock.m_opCode == COMMIT_PREPARED_TX); + m_player = player; + } + + inline RedoLogTransactionPlayer* GetPlayer() const + { + return m_player; } /** diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.cpp b/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.cpp new file mode 100644 index 000000000..cab4e6bc3 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.cpp @@ -0,0 +1,1013 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mtls_recovery_manager.cpp + * Implements a low footprint multi-threaded recovery manager + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mot_engine.h" +#include "mtls_recovery_manager.h" +#include "checkpoint_utils.h" +#include "checkpoint_manager.h" +#include "pending_txn_logger.h" +#include "utils/memutils.h" + +namespace MOT { +DECLARE_LOGGER(MTLSRecoveryManager, Recovery); + +static const char* const MTLS_PROCESSOR_NAME = "MTLSProcessor"; +static const char* const MTLS_COMMITTER_NAME = "MTLSCommitter"; +static constexpr uint32_t WAIT_FOR_PLAYER_TIMEOUT = 1000 * 1000 * 10; // 10 sec +static constexpr uint32_t WAIT_FOR_PLAYER_WARNING_LOG_THRESHOLD = 1000 * 1000; // 1 sec +static constexpr uint32_t QUEUE_MAX_CAPACITY_THRESHOLD = 80; +static constexpr uint32_t QUEUE_MAX_CAPACITY_TIMEOUT = 1000 * 1000 * 10; // 10 sec + +static void TransactionProcessorThread(MTLSTransactionProcessorContext* context) +{ + MOT_ASSERT(context != nullptr); + MemoryContextInit(); + MOT_DECLARE_NON_KERNEL_THREAD(); + char name[ThreadContext::THREAD_NAME_LEN]; + errno_t rc = snprintf_s(name, + ThreadContext::THREAD_NAME_LEN, + ThreadContext::THREAD_NAME_LEN - 1, + "%s%u", + MTLS_PROCESSOR_NAME, + context->GetId()); + securec_check_ss(rc, "", ""); + (void)pthread_setname_np(pthread_self(), name); + MTLSTransactionProcessor processor(context); + MOT_LOG_INFO("%s - Starting", name); + processor.Start(); + MemoryContextDestroyAtThreadExit(TopMemoryContext); + MOT_LOG_DEBUG("%s - Exited", name); +} + +static void TransactionCommitterThread(MTLSTransactionCommitterContext* context) +{ + MOT_ASSERT(context != nullptr); + MemoryContextInit(); + MOT_DECLARE_NON_KERNEL_THREAD(); + (void)pthread_setname_np(pthread_self(), MTLS_COMMITTER_NAME); + MTLSTransactionCommitter committer(context); + MOT_LOG_INFO("%s - Starting", MTLS_COMMITTER_NAME); + committer.Start(); + MemoryContextDestroyAtThreadExit(TopMemoryContext); + MOT_LOG_DEBUG("%s - Exited", MTLS_COMMITTER_NAME); +} + +MTLSRecoveryManager::~MTLSRecoveryManager() +{ + StopThreads(); + Destroy(); +} + +bool MTLSRecoveryManager::Initialize() +{ + do { + if (!BaseRecoveryManager::Initialize()) { + break; + } + if (!InitTxnPool()) { + break; + } + if (!InitializeProcessorsContext()) { + break; + } + if (!InitializeCommitterContext()) { + break; + } + MOT_LOG_INFO("MTLSRecoveryManager: Initialized successfully") + return true; + } while (0); + + MOT_LOG_ERROR("MTLSRecoveryManager: Failed to initialize"); + return false; +} + +void MTLSRecoveryManager::Cleanup() +{ + MOT_ASSERT(m_initialized); + CleanupProcessorsContext(); + CleanupCommitterContext(); + CleanupTxnMap(); + m_extToInt.clear(); + CleanupTxnPool(); +} + +void MTLSRecoveryManager::Destroy() +{ + if (!m_initialized) { + return; + } + DestroyProcessorsContext(); + DestroyCommitterContext(); + CleanupTxnMap(); + DestroyTxnPool(); + m_initialized = false; +} + +bool MTLSRecoveryManager::RecoverDbStart() +{ + MOT_LOG_INFO("Starting MOT recovery"); + return BaseRecoveryManager::RecoverDbStart(); +} + +bool MTLSRecoveryManager::RecoverDbEnd() +{ + Flush(); + StopThreads(); + if (m_maxCsn) { + GetCSNManager().SetCSN(m_maxCsn); + } + + /* Take MTLSRecoveryManager lock to protect it against checkpoint. */ + const std::lock_guard lock(m_lock); + if (!m_txnMap.empty()) { + MOT_LOG_WARN("MTLSRecoveryManager::RecoverDbEnd - txnMap contains pending transactions!"); + } + if (!m_extToInt.empty()) { + MOT_LOG_WARN("MTLSRecoveryManager::RecoverDbEnd - extToInt map is not empty!"); + } + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + AddSurrogateArrayToList(*(m_processors[i]->GetSurrogateStatePtr())); + } + Cleanup(); + return BaseRecoveryManager::RecoverDbEnd(); +} + +bool MTLSRecoveryManager::StartThreads() +{ + do { + if (!StartProcessors()) { + break; + } + if (!StartCommitter()) { + break; + } + return true; + } while (0); + return false; +} + +void MTLSRecoveryManager::StopThreads() +{ + m_notifier.Notify(ThreadNotifier::ThreadState::TERMINATE); + for (auto& worker : m_threads) { + if (worker.joinable()) { + worker.join(); + (void)--m_numThreads; + } + } + if (m_numThreads.load()) { + MOT_LOG_WARN("MTLSRecoveryManager: Failed to stop all threads"); + m_numThreads.store(0); + } + m_threads.clear(); +} + +RedoLogTransactionPlayer* MTLSRecoveryManager::AssignPlayer(LogSegment* segment) +{ + // In case of upgrade from 1VCC to MVCC, we use INITIAL_CSN, because the CSN in the 1VCC log segment + // is not compatible with envelope CSN. + uint64_t csn = (Is1VCCLogSegment(segment) ? INITIAL_CSN : segment->m_controlBlock.m_csn); + uint64_t inId = segment->m_controlBlock.m_internalTransactionId; + uint64_t exId = segment->m_controlBlock.m_externalTransactionId; + RedoLogTransactionPlayer* player = nullptr; + uint32_t waitedUs = 0; + + if (!IsMotOnlyTransaction(segment)) { + MOT_ASSERT(exId != INVALID_TRANSACTION_ID); + MOT_LOG_TRACE("MTLSRecoveryManager::AssignPlayer: Inserting TXN [%lu:%lu] into extToInt map", inId, exId); + m_extToInt[exId] = inId; + } + auto it = (m_txnMap.find(inId)); + if (it != m_txnMap.end()) { + player = it->second; + if (player == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::AssignPlayer: Null player"); + return nullptr; + } + MOT_LOG_TRACE("Found player [%p:%lu:%lu] in txnMap for TXN [%lu:%lu], segment [%p:%u:%lu:%lu]", + player, + player->GetTransactionId(), + player->GetPrevId(), + inId, + exId, + segment, + segment->m_controlBlock.m_opCode, + segment->m_controlBlock.m_csn, + segment->m_replayLsn); + MOT_ASSERT(player->GetCSN() == csn && player->GetTransactionId() == inId && player->GetExternalId() == exId && + !player->m_inPool && player->GetPrevId() == INVALID_TRANSACTION_ID); + player->SetReplayLSN(segment->m_replayLsn); + } else { + while (true) { + if (waitedUs > WAIT_FOR_PLAYER_TIMEOUT) { + MOT_LOG_ERROR("MTLSRecoveryManager::AssignPlayer: Timed out after waiting for %uus", waitedUs); + return nullptr; + } + uint32_t queueId = ComputeProcessorQueueId(segment->m_controlBlock); + player = GetPlayerFromPool(queueId); + if (player == nullptr) { + (void)usleep(ThreadContext::THREAD_SLEEP_TIME_US); + waitedUs += ThreadContext::THREAD_SLEEP_TIME_US; + if (waitedUs % WAIT_FOR_PLAYER_WARNING_LOG_THRESHOLD == 0) { + MOT_LOG_WARN("MTLSRecoveryManager::AssignPlayer: Waiting for %uus to get a player", waitedUs); + } + continue; + } + + /* + * To avoid contention, the committer thread will not erase the txn entry from the txnMap. Instead, + * it will just set the m_prevId in the player (to indicate that the TXN is committed) and + * release it back to the txnPool. + * We erase the txn entry later in the following cases: + * 1. Next time when the player is used in AssignPlayer. + * 2. In SerializePendingRecoveryData during checkpoint. + * Downside of this approach is that the txn entry stays longer in the map. On the other hand, we avoid + * - Contention between the committer thread and envelope's thread which calls the MOTRedo. + * - Avoid deadlock by trying to acquire recovery manager lock for accessing the txnMap. + */ + if (player->GetPrevId() != INVALID_TRANSACTION_ID) { + (void)m_txnMap.erase(player->GetPrevId()); + player->SetPrevId(INVALID_TRANSACTION_ID); + } + + // We only need to insert to the map if this is not a MOT only transaction or + // a transaction that consists of more than one log segment + if (!IsCommitOp(segment->m_controlBlock.m_opCode) || !IsMotOnlyTransaction(segment)) { + MOT_LOG_TRACE("Inserting player %p into txnMap for TXN [%lu:%lu]", player, inId, exId); + m_txnMap[inId] = player; + } + MOT_LOG_TRACE("Assigning player %p for TXN [%lu:%lu], segment [%p:%u:%lu:%lu]", + player, + inId, + exId, + segment, + segment->m_controlBlock.m_opCode, + segment->m_controlBlock.m_csn, + segment->m_replayLsn); + + player->InitRedoTransactionData(inId, exId, csn, segment->m_replayLsn); + player->SetFirstSegment(true); + break; + } + } + return player; +} + +bool MTLSRecoveryManager::ApplyLogSegmentData(char* data, size_t len, uint64_t replayLsn) +{ + const std::lock_guard lock(m_lock); + + MOT_LOG_DEBUG("Apply lsn %llx", replayLsn); + if (m_numThreads.load() == 0 && StartThreads() == false) { + MOT_LOG_ERROR("ApplyLogSegmentFromData - Failed to start recovery threads"); + return false; + } + + SetLastReplayLsn(replayLsn); + + char* curData = data; + while (data + len > curData) { + if (m_errorSet.load() == true) { + MOT_LOG_ERROR("ApplyLogSegmentFromData - Encountered an error during recovery"); + return false; + } + + RedoLogTransactionIterator iterator(curData, len); + uint32_t queueId = ComputeProcessorQueueId(iterator.GetEndSegmentBlock()); + if (!FlowControl(queueId)) { + MOT_LOG_ERROR("ApplyLogSegmentFromData - Timeout"); + return false; + } + + LogSegment* segment = iterator.AllocRedoSegment(replayLsn, m_processors[queueId]->GetAllocator()); + if (segment == nullptr) { + MOT_LOG_ERROR("ApplyLogSegmentFromData - Failed to allocate log segment"); + m_processors[queueId]->PrintInfo(); + return false; + } + m_processors[queueId]->IncAlloc(segment->m_len); + OperationCode opCode = segment->m_controlBlock.m_opCode; + if (opCode >= OperationCode::INVALID_OPERATION_CODE) { + MOT_LOG_ERROR("ApplyLogSegmentFromData - Encountered a bad opCode %u", opCode); + delete segment; + return false; + } + if (!ProcessSegment(segment)) { + MOT_LOG_ERROR("ApplyLogSegmentFromData: Failed to process log segment"); + delete segment; + return false; + } + curData += iterator.GetRedoTransactionLength(); + } + m_notifier.Notify(ThreadNotifier::ThreadState::ACTIVE); + return true; +} + +bool MTLSRecoveryManager::ProcessSegment(LogSegment* segment) +{ + uint64_t intTxnId = segment->m_controlBlock.m_internalTransactionId; + uint32_t queueId = ComputeProcessorQueueId(segment->m_controlBlock); + RedoLogTransactionPlayer* player = AssignPlayer(segment); + if (player == nullptr) { + MOT_LOG_ERROR("ProcessSegment - Failed to assign a player for the segment"); + return false; + } + + /* Set the player in the segment. */ + segment->SetPlayer(player); + + player->GetTxn()->SetInternalTransactionId(intTxnId); + + /* Update the max transaction id. */ + SetMaxTransactionId(intTxnId); + + if (IsDDLTransaction(segment) || HasUpdateIndexColumn(segment) || + (Is1VCCLogSegment(segment) && player->IsFirstSegment())) { + /* + * In case of a DDL transaction we will flush in order to avoid a case when + * a DDL (create index for example) is processed in parallel to a big insert + * like generate series that might not be indexed because of that. + */ + MOT_LOG_TRACE("MTLSRecoveryManager::ProcessSegment - Draining Committer"); + DrainCommitter(); + } + + /* + * Commit only if this is not a cross transaction, since cross transactions + * will be committed by the commit callback. + */ + if (IsCommitOp(segment->m_controlBlock.m_opCode) && IsMotOnlyTransaction(segment)) { + MOT_LOG_TRACE("ProcessSegment - Putting player [%p:%lu:%lu] to the commit queue", + player, + player->GetTransactionId(), + player->GetPrevId()); + if (!m_committer->QueuePut(player)) { + MOT_LOG_ERROR("ProcessSegment - Failed to put the player [%p:%lu:%lu] to the commit queue", + player, + player->GetTransactionId(), + player->GetPrevId()); + if (m_txnMap.find(player->GetTransactionId()) == m_txnMap.end()) { + /* + * This is a MOT only transaction having only one log segment, which was not put in the txn map. + * Caution: We should not put it back to the txnPool as it is a SPSCQueue (Committer is the producer + * and this thread is the consumer). + * Nothing is replayed yet in this transaction. It is safe to directly delete without rolling back the + * transaction. + */ + MOT_LOG_TRACE("ProcessSegment - Deleting player [%p:%lu:%lu] directly on error", + player, + player->GetTransactionId(), + player->GetPrevId()); + delete player; + m_numAllocatedPlayers--; + } + return false; + } + SetMaxCsn(segment->m_controlBlock.m_csn); + } + + if (!m_processors[queueId]->QueuePut(segment)) { + /* Player will be either in the txnMap or the commit queue. */ + MOT_LOG_ERROR("ProcessSegment - Failed to put the player [%p:%lu:%lu] to the processor queue", + player, + player->GetTransactionId(), + player->GetPrevId()); + return false; + } + return true; +} + +void MTLSRecoveryManager::DrainProcessors() +{ + m_notifier.Notify(ThreadNotifier::ThreadState::ACTIVE); + uint32_t waitedUs = 0; + bool shouldWait = true; + while (shouldWait) { + shouldWait = false; + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + if (!m_processors[i]->QueueEmpty()) { + (void)usleep(ThreadContext::THREAD_SLEEP_TIME_US); + waitedUs += ThreadContext::THREAD_SLEEP_TIME_US; + shouldWait = true; + break; + } + } + if (!shouldWait) { + return; + } + if (waitedUs >= ThreadContext::THREAD_START_TIMEOUT_US) { + MOT_LOG_WARN("DrainProcessors: processor queues are not empty"); + return; + } + } +} + +void MTLSRecoveryManager::Flush() +{ + MOT_LOG_TRACE("MTLSRecoveryManager::Flush - Draining Processors and Committer"); + DrainProcessors(); + DrainCommitter(); +} + +void MTLSRecoveryManager::DrainCommitter() +{ + m_notifier.Notify(ThreadNotifier::ThreadState::ACTIVE); + while (!m_committer->QueueEmpty() && !m_errorSet) { + (void)usleep(ThreadContext::THREAD_SLEEP_TIME_US); + } +} + +bool MTLSRecoveryManager::CommitTransaction(uint64_t extTxnId) +{ + uint64_t intTxnId = INVALID_TRANSACTION_ID; + const std::lock_guard lock(m_lock); + auto it = m_extToInt.find(extTxnId); + if (it != m_extToInt.end()) { + intTxnId = it->second; + (void)m_extToInt.erase(it); + MOT_LOG_TRACE( + "MTLSRecoveryManager::CommitTransaction - Removing TXN [%lu:%lu] from extToInt map", intTxnId, extTxnId); + } + if (intTxnId != INVALID_TRANSACTION_ID) { + auto txnIt = m_txnMap.find(intTxnId); + if (txnIt != m_txnMap.end()) { + RedoLogTransactionPlayer* player = txnIt->second; + if (!player) { + MOT_LOG_ERROR( + "MTLSRecoveryManager::CommitTransaction - Null player for TXN [%lu:%lu]", intTxnId, extTxnId); + return false; + } + + MOT_LOG_TRACE("MTLSRecoveryManager::CommitTransaction - Putting player [%p:%lu:%lu] for TXN [%lu:%lu] " + "to the commit queue", + player, + player->GetTransactionId(), + player->GetPrevId(), + intTxnId, + extTxnId); + + MOT_ASSERT(!player->m_inPool); + MOT_ASSERT(player->GetTransactionId() == player->GetTxn()->GetInternalTransactionId()); + if (!m_committer->QueuePut(player)) { + MOT_LOG_ERROR("MTLSRecoveryManager::CommitTransaction - Failed to put player [%p:%lu:%lu] for " + "TXN [%lu:%lu] to the commit queue", + player, + player->GetTransactionId(), + player->GetPrevId(), + intTxnId, + extTxnId); + return false; + } + + MOT_LOG_TRACE("MTLSRecoveryManager::CommitTransaction - Draining Committer"); + DrainCommitter(); + return true; + } else { + MOT_LOG_ERROR( + "MTLSRecoveryManager::CommitTransaction - No player found for TXN [%lu:%lu]", intTxnId, extTxnId); + return false; + } + } + return true; +} + +bool MTLSRecoveryManager::InitializeCommitterContext() +{ + m_committer = new (std::nothrow) MTLSTransactionCommitterContext(this, &m_notifier, m_confQueueSize); + if (m_committer == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::InitializeProcessorsContext: failed to allocate committer"); + return false; + } + if (!m_committer->Initialize()) { + MOT_LOG_ERROR("MTLSRecoveryManager::InitializeProcessorsContext: failed to init committer"); + delete m_committer; + m_committer = nullptr; + return false; + } + return true; +} + +void MTLSRecoveryManager::CleanupCommitterContext() +{ + if (m_committer != nullptr) { + m_committer->Cleanup(); + } +} + +void MTLSRecoveryManager::DestroyCommitterContext() +{ + if (m_committer != nullptr) { + delete m_committer; + m_committer = nullptr; + } +} + +bool MTLSRecoveryManager::StartCommitter() +{ + m_threads.push_back(std::thread(TransactionCommitterThread, m_committer)); + if (!WaitForThreadStart(m_committer)) { + MOT_LOG_ERROR("MTLSRecoveryManager::StartProcessors: Failed to start committer"); + return false; + } + (void)++m_numThreads; + return true; +} + +bool MTLSRecoveryManager::InitializeProcessorsContext() +{ + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + MTLSTransactionProcessorContext* processor = + new (std::nothrow) MTLSTransactionProcessorContext(this, &m_notifier, i, m_confQueueSize); + if (processor == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::InitializeProcessorsContext: failed to init processor %u", i); + return false; + } + if (!processor->Initialize()) { + MOT_LOG_ERROR("MTLSRecoveryManager::InitializeProcessorsContext: failed to init processor %u", i); + delete processor; + return false; + } + m_processors.push_back(processor); + } + return true; +} + +void MTLSRecoveryManager::CleanupProcessorsContext() +{ + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + if (m_processors[i] != nullptr) { + m_processors[i]->Cleanup(); + } + } +} + +void MTLSRecoveryManager::DestroyProcessorsContext() +{ + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + if (m_processors[i] != nullptr) { + delete m_processors[i]; + } + } + m_processors.clear(); +} + +bool MTLSRecoveryManager::StartProcessors() +{ + bool result = true; + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + m_threads.push_back(std::thread(TransactionProcessorThread, m_processors[i])); + if (!WaitForThreadStart(m_processors[i])) { + result = false; + MOT_LOG_ERROR("MTLSRecoveryManager::StartProcessors: Failed to start processor %u", i); + break; + } + (void)++m_numThreads; + } + return result; +} + +RedoLogTransactionPlayer* MTLSRecoveryManager::CreatePlayer() +{ + RedoLogTransactionPlayer* player = new (std::nothrow) RedoLogTransactionPlayer(this); + if (player == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::CreatePlayer: failed to create player object"); + return nullptr; + } + void* txnMem = malloc(sizeof(TxnManager)); + if (txnMem == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::CreatePlayer: failed to allocate txn manager"); + delete player; + return nullptr; + } + TxnManager* txn = new (txnMem) TxnManager(nullptr, true); + if (!txn->Init(0, 0, true)) { + MOT_LOG_ERROR("MTLSRecoveryManager::CreatePlayer: failed to init txn manager"); + free(txnMem); + delete player; + return nullptr; + } + txn->GcAddSession(); + txn->GetGcSession()->SetGcType(GcManager::GC_TYPE::GC_RECOVERY); + player->Init(txn); + return player; +} + +bool MTLSRecoveryManager::InitTxnPool() +{ + m_txnPool = (SPSCQueue**)calloc( + m_confNumProcessors, sizeof(SPSCQueue*)); + if (m_txnPool == nullptr) { + return false; + } + + m_processorPlayerCounts = (uint32_t*)calloc(m_confNumProcessors, sizeof(uint32_t)); + if (m_processorPlayerCounts == nullptr) { + free(m_txnPool); + m_txnPool = nullptr; + return false; + } + + uint32_t queueSize = ComputeNearestHighPow2(m_processorQueueSize); + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + m_txnPool[i] = new (std::nothrow) SPSCQueue(queueSize); + if (m_txnPool[i] == nullptr) { + DestroyTxnPool(); + return false; + } + if (!m_txnPool[i]->Init()) { + DestroyTxnPool(); + return false; + } + } + + return true; +} + +uint64_t MTLSRecoveryManager::SerializePendingRecoveryData(int fd) +{ + uint64_t retErr = (uint64_t)(-1); + uint64_t numTxns = 0; + if (fd < 0) { + MOT_LOG_ERROR("MTLSRecoveryManager::SerializePendingRecoveryData: bad fd"); + return retErr; + } + + PendingTxnLogger logger; + if (!logger.Init()) { + MOT_LOG_ERROR("MTLSRecoveryManager::SerializePendingRecoveryData: failed to init logger"); + return retErr; + } + + auto it = m_txnMap.begin(); + while (it != m_txnMap.end()) { + RedoLogTransactionPlayer* player = it->second; + if (player == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::SerializePendingRecoveryData: unexpected null player"); + return retErr; + } + + /* + * To avoid contention, the committer thread will not erase the txn entry from the txnMap. Instead, + * it will just set the m_prevId in the player (to indicate that the TXN is committed) and + * release it back to the txnPool. + * We erase the txn entry later in the following cases: + * 1. Next time when the player is used in AssignPlayer. + * 2. In SerializePendingRecoveryData during checkpoint. + * Downside of this approach is that the txn entry stays longer in the map. On the other hand, we avoid + * - Contention between the committer thread and envelope's thread which calls the MOTRedo. + * - Avoid deadlock by trying to acquire recovery manager lock for accessing the txnMap. + */ + if (player->GetPrevId() != INVALID_TRANSACTION_ID) { + MOT_LOG_DEBUG( + "MTLSRecoveryManager::SerializePendingRecoveryData: txn %lu is committed", player->GetPrevId()); + MOT_ASSERT(player->GetPrevId() == player->GetTransactionId()); + it = m_txnMap.erase(it); + player->SetPrevId(INVALID_TRANSACTION_ID); + continue; + } + + if (logger.SerializePendingTransaction(player->GetTxn(), fd) != RC_OK) { + MOT_LOG_ERROR("MTLSRecoveryManager::SerializePendingRecoveryData: failed to serialize"); + return retErr; + } + + MOT_LOG_INFO("MTLSRecoveryManager::SerializePendingRecoveryData serialized %d (%d) (%lu)", + numTxns + 1, + player->IsProcessed(), + player->GetTransactionId()); + ++numTxns; + (void)++it; + } + MOT_LOG_TRACE("MTLSRecoveryManager::SerializePendingRecoveryData fd: %d txns: %lu", fd, numTxns); + return numTxns; +} + +bool MTLSRecoveryManager::DeserializePendingRecoveryData(int fd) +{ + MOT_LOG_INFO("MTLSRecoveryManager::DeserializePendingRecoveryData %d", fd); + if (m_numThreads.load() == 0 && StartThreads() == false) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to start recovery threads"); + return false; + } + + uint32_t metaVersion = CheckpointControlFile::GetCtrlFile()->GetMetaVersion(); + uint32_t readEntries = 0; + size_t bufSize = 0; + char* buf = nullptr; + bool result = false; + while (true) { + size_t readSize = 0; + PendingTxnLogger::Header header; + + result = false; + if (metaVersion < METADATA_VER_LOW_RTO) { + CheckpointUtils::TpcEntryHeader tpcHeader; + readSize = CheckpointUtils::ReadFile(fd, (char*)&tpcHeader, sizeof(CheckpointUtils::TpcEntryHeader)); + if (readSize == 0) { + MOT_LOG_INFO("DeserializePendingRecoveryData: TpcFile EOF, read %d entries", readEntries); + result = true; + break; + } else if (readSize != sizeof(CheckpointUtils::TpcEntryHeader)) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to read TpcEntryHeader", + readSize, + errno, + gs_strerror(errno)); + break; + } + header.m_magic = tpcHeader.m_magic; + header.m_len = tpcHeader.m_len; + header.m_replayLsn = 0; + } else { + readSize = CheckpointUtils::ReadFile(fd, (char*)&header, sizeof(PendingTxnLogger::Header)); + if (readSize == 0) { + MOT_LOG_INFO("DeserializePendingRecoveryData: PendingTxnDataFile EOF, read %d entries", readEntries); + result = true; + break; + } else if (readSize != sizeof(PendingTxnLogger::Header)) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to read TransactionLogger::Header", + readSize, + errno, + gs_strerror(errno)); + break; + } + } + + if (header.m_magic != PendingTxnLogger::MAGIC_NUMBER || + header.m_len > RedoLogBuffer::REDO_DEFAULT_BUFFER_SIZE) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Bad entry %lx - %lu", header.m_magic, header.m_len); + break; + } + + MOT_LOG_TRACE("DeserializePendingRecoveryData: Entry length %lu", header.m_len); + if (buf == nullptr || header.m_len > bufSize) { + if (buf != nullptr) { + free(buf); + } + buf = (char*)malloc(header.m_len); + bufSize = header.m_len; + } + if (buf == nullptr) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to allocate buffer (%lu bytes)", header.m_len); + break; + } + + if (CheckpointUtils::ReadFile(fd, buf, header.m_len) != header.m_len) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to read data from file (%lu bytes)", header.m_len); + break; + } + + if (!ApplyPendingRecoveryLogSegmentData(buf, header.m_len, header.m_replayLsn, metaVersion)) { + MOT_LOG_ERROR("DeserializePendingRecoveryData: Failed to allocate segment"); + break; + } + + m_checkpointRecovery.SetLastReplayLsn(header.m_replayLsn); + SetLastReplayLsn(header.m_replayLsn); + + readEntries++; + } + + if (buf != nullptr) { + free(buf); + } + + MOT_LOG_INFO("DeserializePendingRecoveryData: readEntries %lu, mapsize %lu", readEntries, m_txnMap.size()); + m_notifier.Notify(ThreadNotifier::ThreadState::ACTIVE); + return result; +} + +bool MTLSRecoveryManager::ApplyPendingRecoveryLogSegmentData( + char* buf, uint64_t len, uint64_t replayLsn, uint32_t metaVersion) +{ + uint32_t queueId = 0; + LogSegment* segment = nullptr; + + if (metaVersion < METADATA_VER_LOW_RTO) { + segment = new (std::nothrow) LogSegment(); + if (segment == nullptr) { + MOT_LOG_ERROR("ApplyPendingRecoveryLogSegmentData (upgrade): Failed to allocate log segment"); + return false; + } + segment->m_replayLsn = replayLsn; + errno_t erc = memcpy_s(&segment->m_len, sizeof(size_t), buf, sizeof(size_t)); + securec_check(erc, "\0", "\0"); + + EndSegmentBlock* controlBlock = + (EndSegmentBlock*)(buf + sizeof(size_t) + segment->m_len - sizeof(EndSegmentBlock)); + queueId = ComputeProcessorQueueId(*controlBlock); + segment->m_data = (char*)(m_processors[queueId]->GetAllocator()->Alloc(segment->m_len * sizeof(char))); + if (segment->m_data == nullptr) { + MOT_LOG_ERROR( + "ApplyPendingRecoveryLogSegmentData (upgrade): Failed to allocate memory for log segment data"); + delete segment; + return false; + } + segment->m_allocator = m_processors[queueId]->GetAllocator(); + segment->Deserialize(buf); + } else { + RedoLogTransactionIterator iterator(buf, len); + queueId = ComputeProcessorQueueId(iterator.GetEndSegmentBlock()); + segment = iterator.AllocRedoSegment(replayLsn, m_processors[queueId]->GetAllocator()); + if (segment == nullptr) { + MOT_LOG_ERROR("ApplyPendingRecoveryLogSegmentData: Failed to allocate log segment"); + return false; + } + } + + m_processors[queueId]->IncAlloc(segment->m_len); + + if (!ProcessSegment(segment)) { + MOT_LOG_ERROR("ApplyPendingRecoveryLogSegmentData: Failed to process log segment"); + delete segment; + return false; + } + + return true; +} + +bool MTLSRecoveryManager::FlowControl(uint32_t queueId) +{ + uint32_t waitedUs = 0; + while (m_processors[queueId]->GetMemUsagePercentage() >= QUEUE_MAX_CAPACITY_THRESHOLD) { + (void)usleep(1000); + waitedUs += 1000; + if (waitedUs >= QUEUE_MAX_CAPACITY_TIMEOUT) { + MOT_LOG_ERROR("MTLSRecoveryManager: flow control - timeout"); + m_processors[queueId]->PrintInfo(); + return false; + } + } + if (waitedUs) { + MOT_LOG_DEBUG("MTLSRecoveryManager: flow control waited %lu us", waitedUs); + } + return true; +} + +RedoLogTransactionPlayer* MTLSRecoveryManager::GetPlayerFromPool(uint32_t queueId) +{ + RedoLogTransactionPlayer* player = m_txnPool[queueId]->Take(); + if (player == nullptr) { + /* SPSCQueue can hold only maximum of (QUEUE_SIZE - 1) entries. */ + if ((m_numAllocatedPlayers + 1) >= m_confQueueSize) { + MOT_LOG_DEBUG( + "MTLSRecoveryManager::GetPlayerFromPool - Recovery queue limit (%u) reached", m_confQueueSize); + return nullptr; + } + + if ((m_processorPlayerCounts[queueId] + 1) >= m_processorQueueSize) { + MOT_LOG_DEBUG("MTLSRecoveryManager::GetPlayerFromPool - Recovery queue (%u) limit (%u) reached", + queueId, + m_processorQueueSize); + return nullptr; + } + + player = CreatePlayer(); + if (player == nullptr) { + MOT_LOG_ERROR("MTLSRecoveryManager::GetPlayerFromPool - Failed to create player"); + return nullptr; + } + + player->m_queueId = queueId; + + /* + * Caution: Do not put the player to the txnPool (SPSCQueue) as it will break the SPSCQueue. + * This thread is the consumer and committer is the producer of the txnPool, so we should not try to put + * the player to the txnPool. Caller will put the player either to the commit queue (for single-segment MOT + * only txn) or to the txnMap (for multi-segment MOT only txn or cross txn). + */ + MOT_ASSERT(!player->m_inPool); + + /* + * Note: Even though we are not putting the player to the txnPool, we still have to increase the + * m_numAllocatedPlayers in order to respect the configured queue limit. Whenever a player is + * deleted/destroyed, m_numAllocatedPlayers needs to be decremented accordingly. + */ + m_numAllocatedPlayers++; + m_processorPlayerCounts[queueId]++; + MOT_LOG_TRACE("MTLSRecoveryManager::GetPlayerFromPool - Current number of players: %u", m_numAllocatedPlayers); + MOT_LOG_TRACE("MTLSRecoveryManager::GetPlayerFromPool - Allocated new player [%p:%lu:%lu]", + player, + player->GetTransactionId(), + player->GetPrevId()); + if ((m_numAllocatedPlayers + 1) == m_confQueueSize) { + MOT_LOG_WARN("MTLSRecoveryManager::GetPlayerFromPool - Recovery queue limit (%u) reached", m_confQueueSize); + } + } else { + MOT_ASSERT(player->m_inPool); + player->m_inPool = false; + MOT_LOG_TRACE("MTLSRecoveryManager::GetPlayerFromPool - Got player [%p:%lu:%lu] from the txnPool", + player, + player->GetTransactionId(), + player->GetPrevId()); + } + return player; +} + +void MTLSRecoveryManager::CleanupTxnPool() +{ + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnPool - Cleaning txnPool"); + uint32_t deleteCount = 0; + if (m_txnPool != nullptr) { + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + if (m_txnPool[i] == nullptr) { + continue; + } + while (m_txnPool[i]->Top() != nullptr) { + RedoLogTransactionPlayer* player = m_txnPool[i]->Take(); + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnPool - Deleting player %p", player); + SessionContext::SetTxnContext(player->GetTxn()); + delete player; + m_numAllocatedPlayers--; + deleteCount++; + } + } + } + + if (m_processorPlayerCounts != nullptr) { + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + m_processorPlayerCounts[i] = 0; + } + } + + MOT_ASSERT(m_numAllocatedPlayers == 0); + m_numAllocatedPlayers = 0; + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnPool - Deleted %u players from the txnPool", deleteCount); +} + +void MTLSRecoveryManager::DestroyTxnPool() +{ + CleanupTxnPool(); + if (m_txnPool != nullptr) { + for (uint32_t i = 0; i < m_confNumProcessors; i++) { + if (m_txnPool[i] == nullptr) { + continue; + } + delete m_txnPool[i]; + m_txnPool[i] = nullptr; + } + + free(m_txnPool); + m_txnPool = nullptr; + } + + if (m_processorPlayerCounts != nullptr) { + free(m_processorPlayerCounts); + m_processorPlayerCounts = nullptr; + } +} + +void MTLSRecoveryManager::CleanupTxnMap() +{ + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnMap - Cleaning txnMap"); + uint32_t releaseCount = 0; + auto it = m_txnMap.begin(); + while (it != m_txnMap.end()) { + RedoLogTransactionPlayer* player = it->second; + if (player != nullptr && player->GetPrevId() == INVALID_TRANSACTION_ID) { + it = m_txnMap.erase(it); + + /* + * First rollback the transaction and then release the player back to the pool. + * Caution: This is necessary for GC to work properly. Rollback and delete transaction object cannot happen + * together. First phase, we need to rollback all the transactions and then all the transactions objects + * can be deleted. + * We can safely release the player back to the txnPool, because at this point there is no committer thread + * and hence we don't mess with the SPSCQueue. + */ + SessionContext::SetTxnContext(player->GetTxn()); + player->GetTxn()->Rollback(); + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnMap - Releasing player [%p:%lu:%lu] back to the txnPool", + player, + player->GetTransactionId(), + player->GetPrevId()); + ReleasePlayer(player); + releaseCount++; + continue; + } + (void)++it; + } + m_txnMap.clear(); + MOT_LOG_TRACE("MTLSRecoveryManager::CleanupTxnMap - Released %u players back to the txnPool", releaseCount); +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.h b/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.h new file mode 100644 index 000000000..a8ccd7937 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.h @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mtls_recovery_manager.h + * Implements a low footprint multi-threaded recovery manager + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_recovery_manager.h + * + * ------------------------------------------------------------------------- + */ +#ifndef MTLS_RECOVERY_MANAGER_H +#define MTLS_RECOVERY_MANAGER_H + +#include "spsc_allocator.h" +#include "mtls_transaction_processor.h" +#include "mtls_transaction_committer.h" +#include +#include +#include +#include "mot_configuration.h" +#include "base_recovery_manager.h" +#include "surrogate_state.h" +#include "spsc_queue.h" +#include "redo_log_transaction_player.h" +#include "thread_utils.h" + +namespace MOT { +/** + * @class MTLSRecoveryManager + * @brief Implements multi threaded recovery + */ +class MTLSRecoveryManager : public BaseRecoveryManager { +public: + MTLSRecoveryManager() + : m_numThreads(0), + m_confQueueSize(GetGlobalConfiguration().m_parallelRecoveryQueueSize), + m_confNumProcessors(GetGlobalConfiguration().m_parallelRecoveryWorkers), + m_numAllocatedPlayers(0), + m_txnPool(nullptr), + m_processorPlayerCounts(nullptr), + m_processorQueueSize(m_confQueueSize / m_confNumProcessors), + m_committer(nullptr) + {} + + ~MTLSRecoveryManager() override; + + /** + * @brief Performs the necessary tasks to initialize the object. + * @return Boolean value denoting success or failure. + */ + bool Initialize() override; + + /** + * @brief Starts the recovery process. + * @return Boolean value denoting success or failure. + */ + bool RecoverDbStart() override; + + /** + * @brief Performs post recovery operations. + * @return Boolean value denoting success or failure. + */ + bool RecoverDbEnd() override; + + /** + * @brief commits an 'external' transaction. + * @param extTxnId the transaction id. + * @return Boolean value denoting success or failure. + */ + bool CommitTransaction(uint64_t extTxnId) override; + + /** + * @brief Given a log segment, enqueue it in the appropriate queues. + * @return Boolean value denoting success or failure. + */ + bool ProcessSegment(LogSegment* segment); + + /** + * @brief Checks if the operation should be retried. + * @param player the player to check. + * @return Boolean value denoting true or false. + */ + bool ShouldRetryOp(RedoLogTransactionPlayer* player) override + { + // If there was an error or the current transaction is the next one to + // be committed, no point in retrying. + if (IsErrorSet()) { + return false; + } + if (m_committer->QueuePeek() == player) { + if (player->IsRetried()) { + return false; + } else { + // Mark the transaction as retried in order to avoid replaying it again. + player->SetRetried(); + return true; + } + } + return true; + } + + /** + * @brief Gets a txn player object from the pool. Tries to grow the pool upto + * m_confQueueSize if no players are available. + * @return a RedoLogTransactionPlayer object or nullptr if the queue is NA. + */ + RedoLogTransactionPlayer* GetPlayerFromPool(uint32_t queueId); + + void ReleasePlayer(RedoLogTransactionPlayer* player) override + { + MOT_ASSERT(!player->m_inPool); + player->m_inPool = true; + if (!m_txnPool[player->m_queueId]->Put(player)) { + // Cannot happen as the maximum number of players will never exceed the queue's capacity. + MOTAbort(); + } + } + + /** + * @brief Returns the number of currently running threads. + * @return uint32 as the value of num threads + */ + uint32_t GetNumRecoveryThreads() const override + { + return m_numThreads.load(); + } + + /** + * @brief Flushes all queues. + */ + void Flush() override; + + /** + * @brief Flushes the committer's queue. + */ + void DrainCommitter(); + + /** + * @brief Waits up to 60 secs for all processor queues to be empty. + */ + void DrainProcessors(); + + /** + * @brief Initializes the processor threads. + * @return Boolean value denoting success or failure. + */ + bool InitializeProcessorsContext(); + + /** + * @brief Releases the processor thread's resources. + */ + void CleanupProcessorsContext(); + + /** + * @brief Destroys the processor thread contexts. + */ + void DestroyProcessorsContext(); + + /** + * @brief Creates the processor threads and wait for them to start. + * @return Boolean value denoting success or failure. + */ + bool StartProcessors(); + + /** + * @brief Initializes the committer thread. + * @return Boolean value denoting success or failure. + */ + bool InitializeCommitterContext(); + + /** + * @brief Releases the committer thread's resources. + */ + void CleanupCommitterContext(); + + /** + * @brief Destroys the committer thread context. + */ + void DestroyCommitterContext(); + + /** + * @brief Creates the committer thread and wait for it to start. + * @return Boolean value denoting success or failure. + */ + bool StartCommitter(); + + uint64_t SerializePendingRecoveryData(int fd) override; + + bool DeserializePendingRecoveryData(int fd) override; + + void Lock() override + { + m_lock.lock(); + } + + void Unlock() override + { + m_lock.unlock(); + } + +private: + /** + * @brief Extracts log segments from wal. + * @return Boolean value denoting success or failure. + */ + bool ApplyLogSegmentData(char* data, size_t len, uint64_t replayLsn) override; + + /** + * @brief Cleans up the resources. + */ + void Cleanup(); + + /** + * @brief Destroys the recovery manager object. + */ + void Destroy(); + + /** + * @brief Cleans up the transaction map. + */ + void CleanupTxnMap(); + + /** + * @brief Stops all worker threads. + */ + void StopThreads(); + + /** + * @brief Creates the txn player pool. + * @return Boolean value denoting success or failure. + */ + bool InitTxnPool(); + + /** + * @brief Cleans up the txn player pool. + */ + void CleanupTxnPool(); + + /** + * @brief Destroys the txn player pool. + */ + void DestroyTxnPool(); + + /** + * @brief Starts the processors and committer threads. + * @return Boolean value denoting success or failure. + */ + bool StartThreads(); + + /** + * @brief Creates a new player object and enqueues it in the txn pool. + * @return Boolean value denoting success or failure. + */ + RedoLogTransactionPlayer* CreatePlayer(); + + /** + * @brief Assigns a player for a log segment. + * @param LogSegment the segment to assign a player to. + * @return A player object or nullptr if one could not be obtained on time. + */ + RedoLogTransactionPlayer* AssignPlayer(LogSegment* segment); + + /** + * @brief Computes the processor queue id from EndSegmentBlock. + * @param endSegmentBlock EndSegmentBlock of the segment to be processed. + * @return Processor queue id. + */ + inline uint32_t ComputeProcessorQueueId(const EndSegmentBlock& endSegmentBlock) const + { + // In case of upgrade from 1VCC to MVCC, we fallback to single threaded recovery (queueId 0). + uint32_t queueId = (Is1VCCEndSegmentOpCode(endSegmentBlock.m_opCode) + ? 0 + : (endSegmentBlock.m_internalTransactionId % m_confNumProcessors)); + return queueId; + } + + /** + * @brief Checks if a queue has enough space (less than 80% capacity), if not sleeps until + * it frees up or timeout (10 secs). + * @param queueId the queue's identifier to check. + * @return True flow control check was ok, False - timeout occured. + */ + bool FlowControl(uint32_t queueId); + + /** + * @brief Helper func that extracts a log segment from a buffer according to a meta version. + * @param buf the buffer to extract the data from. + * @param len the length of the buffer. + * @param replayLsn the replayLsn for the segment. + * @param metaVersion the metadata version of the pending transactions data file. + * @return Boolean value denoting success or failure. + */ + bool ApplyPendingRecoveryLogSegmentData(char* buf, uint64_t len, uint64_t replayLsn, uint32_t metaVersion); + + std::vector m_threads; + std::atomic m_numThreads; + uint32_t m_confQueueSize; + uint32_t m_confNumProcessors; + uint32_t m_numAllocatedPlayers; + + /* + * SPSCQueue (per processor) to hold the TXN players. + * Committer is the producer - after the TXN is committed, it releases the player back to the pool. + * Envelope's thread which calls MOTRedo is the consumer - takes a free player from the pool and assign it for a + * new transaction. When there are no free players, it allocates a new player and assign it for a new transaction + * (without putting it to the pool) or waits until a player becomes free if the current number of allocated players + * already reached the configured queue size. + */ + SPSCQueue** m_txnPool; + uint32_t* m_processorPlayerCounts; + uint32_t m_processorQueueSize; + + std::mutex m_lock; + std::map m_txnMap; + std::map m_extToInt; + std::vector m_processors; + MTLSTransactionCommitterContext* m_committer; + ThreadNotifier m_notifier; +}; + +} // namespace MOT +#endif /* MTLS_RECOVERY_MANAGER_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.cpp b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.cpp new file mode 100644 index 000000000..70ce5ffd9 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * transaction_committer.cpp + * Transaction Committer Worker Thread + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mtls_transaction_committer.h" + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(MTLSTransactionCommitterContext, Recovery); +DECLARE_LOGGER(MTLSTransactionCommitter, Recovery); + +static bool Wakeup(void* obj) +{ + MTLSTransactionCommitterContext* ctx = (MTLSTransactionCommitterContext*)obj; + if (ctx != nullptr) { + return ((ctx->GetThreadNotifier()->GetState() == ThreadNotifier::ThreadState::TERMINATE) || + (ctx->GetThreadNotifier()->GetState() == ThreadNotifier::ThreadState::ACTIVE && + ctx->QueuePeek() != nullptr)); + } + return true; +} + +void MTLSTransactionCommitter::Start() +{ + if (m_context == nullptr) { + MOT_LOG_ERROR("MTLSTransactionCommitter::Start - Not initialized"); + return; + } + + SessionContext* sessionContext = + GetSessionManager()->CreateSessionContext(false, 0, nullptr, INVALID_CONNECTION_ID, true); + if (sessionContext == nullptr) { + MOT_LOG_ERROR("MTLSTransactionCommitter::Start - Failed to initialize Session Context"); + m_context->SetError(); + MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); + return; + } + + if (GetGlobalConfiguration().m_enableNuma && !GetTaskAffinity().SetAffinity(MOTCurrThreadId)) { + MOT_LOG_WARN("MTLSTransactionCommitter::Start - Failed to set affinity for the committer worker"); + } + + m_context->SetReady(); + + while (m_context->GetThreadNotifier()->GetState() != ThreadNotifier::ThreadState::TERMINATE) { + if (m_context->GetThreadNotifier()->Wait(Wakeup, (void*)m_context) == ThreadNotifier::ThreadState::TERMINATE) { + MOT_LOG_INFO("MTLSCommitter - Terminating"); + break; + } + + RedoLogTransactionPlayer* player = m_context->QueuePeek(); + if (player != nullptr && player->IsProcessed()) { + SessionContext::SetTxnContext(player->GetTxn()); + if (CommitTransaction(player) != RC_OK) { + MOT_LOG_ERROR("MTLSTransactionCommitter::Start - Commit failed"); + m_context->SetError(); + break; + } + + /* + * To avoid contention, the committer thread will not erase the txn entry from the txnMap. Instead, + * it will just set the m_prevId in the player (to indicate that the TXN is committed) and + * release it back to the txnPool. + * We erase the txn entry later in the following cases: + * 1. Next time when the player is used in AssignPlayer. + * 2. In SerializePendingRecoveryData during checkpoint. + * Downside of this approach is that the txn entry stays longer in the map. On the other hand, we avoid + * - Contention between the committer thread and envelope's thread which calls the MOTRedo. + * - Avoid deadlock by trying to acquire recovery manager lock for accessing the txnMap. + */ + m_context->QueuePop(); + m_context->CleanupTxnAndReleasePlayer(player); + } + } + + GetSessionManager()->DestroySessionContext(sessionContext); + MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); +} + +RC MTLSTransactionCommitter::CommitTransaction(RedoLogTransactionPlayer* player) +{ + MOT_LOG_TRACE("MTLSTransactionCommitter::CommitTransaction Committing TXN [%lu:%lu], NumSegments: %lu", + player->GetTransactionId(), + player->GetExternalId(), + player->GetNumSegs()); + RC status = player->CommitTransaction(); + if (status != RC_OK) { + MOT_LOG_ERROR("MTLSTransactionCommitter::CommitTransaction: commit failed"); + return status; + } + return RC_OK; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.h b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.h new file mode 100644 index 000000000..996793bc9 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * transaction_committer.h + * Transaction Committer Worker Thread + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_committer.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef MTLS_TRANSACTION_COMMITTER_H +#define MTLS_TRANSACTION_COMMITTER_H + +#include "mot_engine.h" +#include "spsc_queue.h" +#include "redo_log_transaction_player.h" +#include "thread_utils.h" + +namespace MOT { +/** + * @class MTLSTransactionCommitterContext + * @brief Defines a context for the MTLSTransactionCommitter Thread. + */ +class MTLSTransactionCommitterContext : public ThreadContext { +public: + MTLSTransactionCommitterContext(IRecoveryManager* recoveryManager, ThreadNotifier* notifier, uint32_t queueSize) + : ThreadContext(), + m_recoveryManager(recoveryManager), + m_notifier(notifier), + m_queueSize(queueSize), + m_queue(queueSize) + {} + + ~MTLSTransactionCommitterContext() + { + Cleanup(); + + // RecoveryManager will be destroyed by the provider + m_recoveryManager = nullptr; + + // Notifier will be destroyed by the provider + m_notifier = nullptr; + } + + bool Initialize() + { + if (!m_queue.Init()) { + MOT_LOG_ERROR("MTLSTransactionCommitterContext::Initialize - Failed to initialize the player queue"); + return false; + } + return true; + } + + void Cleanup() + { + while (m_queue.Top() != nullptr) { + /* + * Committer queue still has a player. That means the transaction is not committed. + * Rollback the transaction first and then release it back to the txnPool. + */ + RedoLogTransactionPlayer* player = m_queue.Take(); + player->GetTxn()->Rollback(); + CleanupTxnAndReleasePlayer(player); + } + } + + inline RedoLogTransactionPlayer* QueuePeek() + { + return m_queue.Top(); + } + + inline bool QueuePut(RedoLogTransactionPlayer* player) + { + return m_queue.Put(player); + } + + inline void QueuePop() + { + m_queue.Pop(); + } + + inline bool QueueEmpty() + { + return m_queue.IsEmpty(); + } + + inline void CleanupTxnAndReleasePlayer(RedoLogTransactionPlayer* player) + { + /* + * Setting prevId as the transactionId to indicate that the TXN is committed and + * so player can be removed from txnMap and reused for new a transaction. + * Caution: We don't want the committer to access the txnMap directly to avoid contention + * and avoid deadlock by trying to acquire recovery manager lock for accessing the txnMap. + */ + player->SetPrevId(player->GetTransactionId()); + player->CleanupTransaction(); + MOT_LOG_TRACE( + "MTLSTransactionCommitter::CleanupTxnAndReleasePlayer - Releasing player [%p:%lu:%lu] back to the txnPool", + player, + player->GetTransactionId(), + player->GetPrevId()); + m_recoveryManager->ReleasePlayer(player); + } + + void SetError() override + { + ThreadContext::SetError(); + m_recoveryManager->SetError(); + } + + inline ThreadNotifier* GetThreadNotifier() + { + return m_notifier; + } + +private: + IRecoveryManager* m_recoveryManager; + ThreadNotifier* m_notifier; + uint32_t m_queueSize; + SPSCQueue m_queue; + + DECLARE_CLASS_LOGGER(); +}; + +/** + * @class MTLSTransactionCommitter + * @brief Transaction Committer Worker Thread + */ +class MTLSTransactionCommitter { +public: + explicit MTLSTransactionCommitter(MTLSTransactionCommitterContext* context) : m_context(context) + {} + + ~MTLSTransactionCommitter() + { + m_context = nullptr; + } + + void Start(); + +private: + RC CommitTransaction(RedoLogTransactionPlayer* player); + + MTLSTransactionCommitterContext* m_context; +}; +} // namespace MOT + +#endif // MTLS_TRANSACTION_COMMITTER_H diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.cpp b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.cpp new file mode 100644 index 000000000..41c12b231 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * transaction_processor.cpp + * Transaction Processor Worker Thread + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mtls_transaction_processor.h" + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(MTLSTransactionProcessorContext, Recovery); +DECLARE_LOGGER(MTLSTransactionProcessor, Recovery); + +static bool Wakeup(void* obj) +{ + MTLSTransactionProcessorContext* ctx = (MTLSTransactionProcessorContext*)obj; + if (ctx != nullptr) { + return ((ctx->GetThreadNotifier()->GetState() == ThreadNotifier::ThreadState::TERMINATE) || + (ctx->GetThreadNotifier()->GetState() == ThreadNotifier::ThreadState::ACTIVE && + ctx->QueuePeek() != nullptr)); + } + return true; +} + +void MTLSTransactionProcessor::Start() +{ + if (m_context == nullptr) { + MOT_LOG_ERROR("MTLSTransactionProcessor::Start: not initialized"); + return; + } + + SessionContext* sessionContext = + GetSessionManager()->CreateSessionContext(false, 0, nullptr, INVALID_CONNECTION_ID, true); + if (sessionContext == nullptr) { + MOT_LOG_ERROR("MTLSTransactionProcessor::Start: Failed to initialize Session Context"); + m_context->SetError(); + MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); + return; + } + + if (GetGlobalConfiguration().m_enableNuma && !GetTaskAffinity().SetAffinity(MOTCurrThreadId)) { + MOT_LOG_WARN("Failed to set affinity for the processor worker"); + } + + m_context->SetReady(); + + while (m_context->GetThreadNotifier()->GetState() != ThreadNotifier::ThreadState::TERMINATE) { + if (m_context->GetThreadNotifier()->Wait(Wakeup, (void*)m_context) == ThreadNotifier::ThreadState::TERMINATE) { + MOT_LOG_INFO("MTLSProcessor%u - Terminating", m_context->GetId()); + break; + } + + LogSegment* segment = m_context->QueuePeek(); + if (segment != nullptr) { + RedoLogTransactionPlayer* player = segment->GetPlayer(); + if (player == nullptr) { + MOT_LOG_ERROR("MTLSTransactionProcessor::Start: no player / segment"); + m_context->SetError(); + break; + } + + SessionContext::SetTxnContext(player->GetTxn()); + player->SetSurrogateState(m_context->GetSurrogateStatePtr()); + if (player->IsFirstSegment()) { + if (player->BeginTransaction() != RC_OK) { + MOT_LOG_ERROR("MTLSTransactionProcessor::Start: failed to start the transaction"); + m_context->SetError(); + break; + } + player->SetFirstSegment(false); + } + + if (player->RedoSegment(segment) != RC_OK) { + MOT_LOG_ERROR("MTLSTransactionProcessor::Start: failed to redo segment"); + m_context->SetError(); + break; + } + + if (IsCommitOp(segment->m_controlBlock.m_opCode)) { + player->MarkProcessed(); + m_context->GetThreadNotifier()->Notify(ThreadNotifier::ThreadState::ACTIVE); + } + + uint64_t slen = segment->m_len; + m_context->IncFreed(slen); + m_context->QueuePop(); + delete segment; + } + } + + GetSessionManager()->DestroySessionContext(sessionContext); + MOT::MOTEngine::GetInstance()->OnCurrentThreadEnding(); +} + +void MTLSTransactionProcessorContext::PrintInfo() const +{ + MOT_LOG_INFO("Processor%u : NumAlloc %lu, NumFreed %lu, Delta %lu, Usage: %u", + m_processorId, + m_sizeAlloc, + m_sizeFreed, + (m_sizeAlloc - m_sizeFreed) / 1024, + GetMemUsagePercentage()); +} + +uint32_t MTLSTransactionProcessorContext::GetMemUsagePercentage() const +{ + double allocDiff = m_sizeAlloc - m_sizeFreed; + double percent = (allocDiff / ALLOCATOR_SIZE) * 100; + return (uint32_t)percent; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.h b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.h new file mode 100644 index 000000000..81668249c --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * transaction_processor.h + * Transaction Processor Worker Thread + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/mtls_transaction_processor.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef MTLS_TRANSACTION_PROCESSOR_H +#define MTLS_TRANSACTION_PROCESSOR_H + +#include "mot_engine.h" +#include "spsc_queue.h" +#include "redo_log_transaction_player.h" +#include "thread_utils.h" + +namespace MOT { +/** + * @class MTLSTransactionProcessorContext + * @brief Defines a context for the MTLSTransactionProcessor Thread. + */ +class MTLSTransactionProcessorContext : public ThreadContext { +public: + MTLSTransactionProcessorContext( + IRecoveryManager* recoveryManager, ThreadNotifier* notifier, uint32_t id, uint32_t queueSize) + : ThreadContext(), + m_recoveryManager(recoveryManager), + m_notifier(notifier), + m_processorId(id), + m_queueSize(queueSize), + m_queue(queueSize), + m_allocator(nullptr), + m_sizeAlloc(0), + m_sizeFreed(0) + {} + + ~MTLSTransactionProcessorContext() + { + Cleanup(); + m_recoveryManager = nullptr; + m_notifier = nullptr; + } + + bool Initialize() + { + if (!m_queue.Init()) { + MOT_LOG_ERROR("MTLSTransactionProcessorContext: Failed to initialize the log segment queue"); + return false; + } + if (!m_surrogateState.Init()) { + MOT_LOG_ERROR("MTLSTransactionProcessorContext: Failed to initialize the surrogate state"); + return false; + } + m_allocator = SPSCVarSizeAllocator::GetSPSCAllocator(ALLOCATOR_SIZE); + if (m_allocator == nullptr) { + MOT_LOG_ERROR("MTLSTransactionProcessorContext:: failed to create allocator"); + return false; + } + return true; + } + + void Cleanup() + { + while (m_queue.Top() != nullptr) { + LogSegment* segment = m_queue.Take(); + delete segment; + } + if (m_allocator != nullptr) { + SPSCVarSizeAllocator::FreeSPSCAllocator(m_allocator); + m_allocator = nullptr; + } + } + + inline SPSCVarSizeAllocator* GetAllocator() + { + return m_allocator; + } + + inline bool QueuePut(LogSegment* op) + { + return m_queue.Put(op); + } + + inline LogSegment* QueuePeek() + { + return m_queue.Top(); + } + + inline void QueuePop() + { + m_queue.Pop(); + } + + inline bool QueueEmpty() + { + return m_queue.IsEmpty(); + } + + inline SurrogateState* GetSurrogateStatePtr() + { + return &m_surrogateState; + } + + inline uint32_t GetId() const + { + return m_processorId; + } + + inline void SetId(uint32_t id) + { + m_processorId = id; + } + + void SetError() override + { + ThreadContext::SetError(); + m_recoveryManager->SetError(); + } + + inline void IncAlloc(uint64_t size) + { + m_sizeAlloc += size; + } + + inline void IncFreed(uint64_t size) + { + m_sizeFreed += size; + } + + void PrintInfo() const; + + uint32_t GetMemUsagePercentage() const; + + inline ThreadNotifier* GetThreadNotifier() + { + return m_notifier; + } + + static constexpr uint32_t ALLOCATOR_SIZE = (1024 * 1024 * 100); + +private: + IRecoveryManager* m_recoveryManager; + ThreadNotifier* m_notifier; + uint32_t m_processorId; + uint32_t m_queueSize; + SPSCQueue m_queue; + SPSCVarSizeAllocator* m_allocator; + SurrogateState m_surrogateState; + volatile uint64_t m_sizeAlloc; + volatile uint64_t m_sizeFreed; + + DECLARE_CLASS_LOGGER(); +}; + +/** + * @class MTLSTransactionProcessor + * @brief Transaction Processor Worker Thread + */ +class MTLSTransactionProcessor { +public: + explicit MTLSTransactionProcessor(MTLSTransactionProcessorContext* context) : m_context(context) + {} + + ~MTLSTransactionProcessor() + { + m_context = nullptr; + } + + void Start(); + +private: + MTLSTransactionProcessorContext* m_context; +}; +} // namespace MOT + +#endif // MTLS_TRANSACTION_PROCESSOR_H diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.cpp b/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.cpp deleted file mode 100644 index d3ea4bd30..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.cpp +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * recovery_manager.cpp - * Handles all recovery tasks, including recovery from a checkpoint, xlog and 2PC operations. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/recovery_manager.cpp - * - * ------------------------------------------------------------------------- - */ - -#include -#include -#include -#include "mot_engine.h" -#include "recovery_manager.h" -#include "checkpoint_utils.h" -#include "checkpoint_manager.h" -#include "spin_lock.h" -#include "redo_log_transaction_iterator.h" -#include "mot_engine.h" - -namespace MOT { -DECLARE_LOGGER(RecoveryManager, Recovery); - -bool RecoveryManager::Initialize() -{ - // in a thread-pooled envelope the affinity could be disabled, so we use task affinity here - if (GetGlobalConfiguration().m_enableNuma) { - Affinity& affinity = GetTaskAffinity(); - affinity.SetAffinity(m_threadId); - } - - if (m_enableLogStats) { - m_logStats = new (std::nothrow) LogStats(); - if (m_logStats == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate statistics object"); - return false; - } - } - - if (CheckpointControlFile::GetCtrlFile() == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate ctrlfile object"); - return false; - } - - if (m_surrogateState.IsValid() == false || m_sState.IsValid() == false) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Initialization", "Failed to allocate surrogate state object"); - return false; - } - - m_initialized = true; - return m_initialized; -} - -bool RecoveryManager::RecoverDbStart() -{ - MOT_LOG_INFO("Starting MOT recovery"); - - if (m_recoverFromCkptDone) { - /* - * This is switchover case. - * In case of CASCADE switchover (CASCADE node switchover with STANDBY node), we need to - * set the correct m_lsn to make sure that we skip any redo which are already replayed. - * After switchover, envelope will start sync from the previous ckpt position. - * In MOT engine, we should skip any redo before m_lastReplayLsn. - */ - if (m_lsn < m_lastReplayLsn) { - m_lsn = m_lastReplayLsn; - } - return true; - } - - if (!m_checkpointRecovery.Recover()) { - return false; - } - - m_lsn = m_checkpointRecovery.GetLsn(); - m_recoverFromCkptDone = true; - return true; -} - -bool RecoveryManager::RecoverDbEnd() -{ - if (MOTEngine::GetInstance()->GetInProcessTransactions().GetNumTxns() != 0) { - MOT_LOG_ERROR("MOT recovery: There are uncommitted or incomplete transactions, " - "ignoring and clearing those log segments."); - MOTEngine::GetInstance()->GetInProcessTransactions().Clear(); - } - - if (m_sState.IsEmpty() == false) { - AddSurrogateArrayToList(m_sState); - } - - // set global commit sequence number - GetCSNManager().SetCSN(m_maxRecoveredCsn); - - // merge and apply all SurrogateState maps - SurrogateState::Merge(m_surrogateList, m_surrogateState); - m_surrogateList.clear(); - ApplySurrogate(); - - if (m_enableLogStats && m_logStats != nullptr) { - m_logStats->Print(); - } - - bool success = !m_errorSet; - MOT_LOG_INFO("MOT recovery %s", success ? "completed" : "failed"); - return success; -} - -void RecoveryManager::CleanUp() -{ - if (!m_initialized) { - return; - } - - if (m_logStats != nullptr) { - delete m_logStats; - m_logStats = nullptr; - } - - m_initialized = false; -} - -bool RecoveryManager::ApplyRedoLog(uint64_t redoLsn, char* data, size_t len) -{ - if (redoLsn <= m_lsn) { - // ignore old redo records which are prior to our checkpoint LSN - MOT_LOG_DEBUG("ApplyRedoLog - ignoring old redo record. Checkpoint LSN: %lu, redo LSN: %lu", m_lsn, redoLsn); - return true; - } - return ApplyLogSegmentFromData(data, len, redoLsn); -} - -bool RecoveryManager::ApplyLogSegmentFromData(char* data, size_t len, uint64_t replayLsn /* = 0 */) -{ - bool result = false; - char* curData = data; - - while (data + len > curData) { - // obtain LogSegment from buffer - RedoLogTransactionIterator iterator(curData, len); - LogSegment* segment = iterator.AllocRedoSegment(replayLsn); - if (segment == nullptr) { - MOT_LOG_ERROR("ApplyLogSegmentFromData - failed to allocate segment"); - return false; - } - - // check LogSegment Op validity - OperationCode opCode = segment->m_controlBlock.m_opCode; - if (opCode >= OperationCode::INVALID_OPERATION_CODE) { - MOT_LOG_ERROR("ApplyLogSegmentFromData - encountered a bad opCode %u", opCode); - delete segment; - return false; - } - - // build operation params - uint64_t inId = segment->m_controlBlock.m_internalTransactionId; - uint64_t exId = segment->m_controlBlock.m_externalTransactionId; - RecoveryOps::RecoveryOpState recoveryState = - IsCommitOp(opCode) ? RecoveryOps::RecoveryOpState::COMMIT : RecoveryOps::RecoveryOpState::ABORT; - MOT_LOG_DEBUG("ApplyLogSegmentFromData: opCode %u, externalTransactionId %lu, internalTransactionId %lu", - opCode, - exId, - inId); - - // insert the segment (if not abort) - if (!IsAbortOp(opCode) && !MOTEngine::GetInstance()->GetInProcessTransactions().InsertLogSegment(segment)) { - MOT_LOG_ERROR("ApplyLogSegmentFromData - insert log segment failed"); - delete segment; - return false; - } - - // operate on the transaction if: - // 1. abort - // 2. mot transaction (exid = 0) - // 3. regular transaction that's committed in the clog - if (IsAbortOp(opCode) || - (IsCommitOp(opCode) && (IsMotTransactionId(segment) || IsTransactionIdCommitted(exId)))) { - result = OperateOnRecoveredTransaction(inId, exId, recoveryState); - if (IsAbortOp(opCode)) { - delete segment; - } - if (!result) { - MOT_LOG_ERROR("ApplyLogSegmentFromData - operateOnRecoveredTransaction failed (abort)"); - return false; - } - } else { - MOT_LOG_DEBUG("ApplyLogSegmentFromData: Added to map, opCode %u, externalTransactionId %lu, " - "internalTransactionId %lu", - opCode, - exId, - inId); - } - curData += iterator.GetRedoTransactionLength(); - } - return true; -} - -bool RecoveryManager::CommitRecoveredTransaction(uint64_t externalTransactionId) -{ - uint64_t internalId = 0; - if (MOTEngine::GetInstance()->GetInProcessTransactions().FindTransactionId(externalTransactionId, internalId)) { - return OperateOnRecoveredTransaction(internalId, externalTransactionId, RecoveryOps::RecoveryOpState::COMMIT); - } - return true; -} - -bool RecoveryManager::OperateOnRecoveredTransaction( - uint64_t internalTransactionId, uint64_t externalTransactionId, RecoveryOps::RecoveryOpState rState) -{ - RC status = RC_OK; - if (rState != RecoveryOps::RecoveryOpState::ABORT) { - auto operateLambda = [this](RedoLogTransactionSegments* segments, uint64_t id) -> RC { - RC redoStatus = RC_OK; - LogSegment* segment = segments->GetSegment(segments->GetCount() - 1); - uint64_t csn = segment->m_controlBlock.m_csn; - for (uint32_t i = 0; i < segments->GetCount(); i++) { - segment = segments->GetSegment(i); - redoStatus = RedoSegment(segment, csn, id, RecoveryOps::RecoveryOpState::COMMIT); - if (redoStatus != RC_OK) { - MOT_LOG_ERROR("OperateOnRecoveredTransaction failed with rc %d", redoStatus); - return redoStatus; - } - } - return redoStatus; - }; - - status = MOTEngine::GetInstance()->GetInProcessTransactions().ForUniqueTransaction( - internalTransactionId, externalTransactionId, operateLambda); - } - if (status != RC_OK) { - MOT_LOG_ERROR("OperateOnRecoveredTransaction: wal recovery failed"); - return false; - } - return true; -} - -RC RecoveryManager::RedoSegment( - LogSegment* segment, uint64_t csn, uint64_t transactionId, RecoveryOps::RecoveryOpState rState) -{ - RC status = RC_OK; - uint8_t* endPosition = (uint8_t*)(segment->m_data + segment->m_len); - uint8_t* operationData = (uint8_t*)(segment->m_data); - bool txnStarted = false; - bool wasCommit = false; - - while (operationData < endPosition) { - // redo log recovery - single threaded - if (IsRecoveryMemoryLimitReached(NUM_REDO_RECOVERY_THREADS)) { - status = RC_ERROR; - MOT_LOG_ERROR("Memory hard limit reached. Cannot recover datanode"); - break; - } - - // begin transaction on-demand - if (!txnStarted) { - if (RecoveryOps::BeginTransaction(MOTCurrTxn, segment->m_replayLsn) != RC_OK) { - status = RC_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recover Redo Segment", "Cannot start a new transaction"); - break; - } - - txnStarted = true; - } - - operationData += RecoveryOps::RecoverLogOperation( - MOTCurrTxn, operationData, csn, transactionId, MOTCurrThreadId, m_sState, status, wasCommit); - - // check operation result status - if (status != RC_OK) { - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recover Redo Segment", "Failed to recover redo segment"); - break; - } - - // update transactional state - if (wasCommit) { - txnStarted = false; - } - } - - // single threaded, no need for locking or CAS - if (csn > m_maxRecoveredCsn) { - m_maxRecoveredCsn = csn; - } - if (status != RC_OK) { - MOT_LOG_ERROR("RecoveryManager::redoSegment: got error %u on tid %lu", status, transactionId); - } - return status; -} - -bool RecoveryManager::LogStats::FindIdx(uint64_t tableId, uint64_t& id) -{ - id = m_numEntries; - std::map::iterator it; - std::lock_guard lock(m_slock); - it = m_idToIdx.find(tableId); - if (it == m_idToIdx.end()) { - Entry* newEntry = new (std::nothrow) Entry(tableId); - if (newEntry == nullptr) { - return false; - } - m_tableStats.push_back(newEntry); - m_idToIdx.insert(std::pair(tableId, m_numEntries)); - m_numEntries++; - } else { - id = it->second; - } - return true; -} - -void RecoveryManager::LogStats::Print() -{ - MOT_LOG_ERROR(">> log recovery stats >>"); - for (uint64_t i = 0; i < m_numEntries; i++) { - MOT_LOG_ERROR("TableId %lu, Inserts: %lu, Updates: %lu, Deletes: %lu", - m_tableStats[i]->m_id, - m_tableStats[i]->m_inserts.load(), - m_tableStats[i]->m_updates.load(), - m_tableStats[i]->m_deletes.load()); - } - MOT_LOG_ERROR("Overall tcls: %lu", m_commits.load()); -} - -void RecoveryManager::SetCsn(uint64_t csn) -{ - uint64_t currentCsn = m_maxRecoveredCsn; - while (currentCsn < csn) { - m_maxRecoveredCsn.compare_exchange_weak(currentCsn, csn); - currentCsn = m_maxRecoveredCsn; - } -} - -void RecoveryManager::AddSurrogateArrayToList(SurrogateState& surrogate) -{ - m_surrogateListLock.lock(); - if (surrogate.IsEmpty() == false) { - uint64_t* newArray = new (std::nothrow) uint64_t[surrogate.GetMaxConnections()]; - if (newArray != nullptr) { - errno_t erc = memcpy_s(newArray, - surrogate.GetMaxConnections() * sizeof(uint64_t), - surrogate.GetArray(), - surrogate.GetMaxConnections() * sizeof(uint64_t)); - securec_check(erc, "\0", "\0"); - m_surrogateList.push_back(newArray); - } - } - m_surrogateListLock.unlock(); -} - -void RecoveryManager::ApplySurrogate() -{ - if (m_surrogateState.IsEmpty()) { - return; - } - - const uint64_t* array = m_surrogateState.GetArray(); - for (int i = 0; i < m_maxConnections; i++) { - GetSurrogateKeyManager()->SetSurrogateSlot(i, array[i]); - } -} - -bool RecoveryManager::IsMotTransactionId(LogSegment* segment) -{ - MOT_ASSERT(segment); - if (segment != nullptr) { - return segment->m_controlBlock.m_externalTransactionId == INVALID_TRANSACTION_ID; - } - return false; -} - -bool RecoveryManager::IsTransactionIdCommitted(uint64_t xid) -{ - MOT_ASSERT(m_clogCallback); - if (m_clogCallback != nullptr) { - return (*m_clogCallback)(xid) == TXN_COMMITED; - } - return false; -} - -bool RecoveryManager::IsRecoveryMemoryLimitReached(uint32_t numThreads) -{ - uint64_t memoryRequiredBytes = (uint64_t)numThreads * MEM_CHUNK_SIZE_MB * MEGA_BYTE; - if (MOTEngine::GetInstance()->GetCurrentMemoryConsumptionBytes() + memoryRequiredBytes >= - MOTEngine::GetInstance()->GetHardMemoryLimitBytes()) { - MOT_LOG_WARN("IsRecoveryMemoryLimitReached: recovery memory limit reached " - "current memory: %lu, required memory: %lu, hard limit memory: %lu", - MOTEngine::GetInstance()->GetCurrentMemoryConsumptionBytes(), - memoryRequiredBytes, - MOTEngine::GetInstance()->GetHardMemoryLimitBytes()); - return true; - } else { - return false; - } -} -} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.h b/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.h deleted file mode 100644 index eecbc7a2c..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager.h +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * recovery_manager.h - * Handles all recovery tasks, including recovery from a checkpoint, xlog and 2PC operations. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/recovery_manager.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef RECOVERY_MANAGER_H -#define RECOVERY_MANAGER_H - -#include -#include "checkpoint_ctrlfile.h" -#include "redo_log_global.h" -#include "redo_log_transaction_iterator.h" -#include "txn.h" -#include "global.h" -#include "mot_configuration.h" -#include "irecovery_manager.h" -#include "surrogate_state.h" -#include "checkpoint_recovery.h" -#include "recovery_ops.h" - -namespace MOT { -/** - * @class RecoveryManager - * @brief handles all recovery tasks, including recovery from - * a checkpoint, xlog and 2 pc operations - */ -class RecoveryManager : public IRecoveryManager { -public: - RecoveryManager() - : m_logStats(nullptr), - m_initialized(false), - m_recoverFromCkptDone(false), - m_lsn(0), - m_lastReplayLsn(0), - m_tid(0), - m_maxRecoveredCsn(0), - m_enableLogStats(GetGlobalConfiguration().m_enableLogRecoveryStats), - m_errorSet(false), - m_clogCallback(nullptr), - m_threadId(AllocThreadId()), - m_maxConnections(GetGlobalConfiguration().m_maxConnections) - {} - - ~RecoveryManager() override - {} - - /** - * @brief Performs the necessary tasks to initialize the object. - * @return Boolean value denoting success or failure. - */ - bool Initialize() override; - - /** - * @brief Cleans up the recovery object. - */ - void CleanUp() override; - - inline void SetCommitLogCallback(CommitLogStatusCallback clogCallback) override - { - this->m_clogCallback = clogCallback; - } - - /** - * @brief Starts the recovery process which currently consists of - * checkpoint recovery. - * @return Boolean value denoting success or failure. - */ - bool RecoverDbStart() override; - - /** - * @brief Performs the post recovery operations: apply the in-process - * transactions and surrogate array, set the max csn and prints the stats - * @return Boolean value denoting success or failure. - */ - bool RecoverDbEnd() override; - - /** - * @brief attempts to insert a data chunk into the in-process - * transactions map and operate on it - * @return Boolean value denoting success or failure. - */ - bool ApplyLogSegmentFromData(char* data, size_t len, uint64_t replayLsn = 0) override; - - /** - * @brief attempts to insert a data chunk into the in-process - * transactions map and operate on it. Checks that the redo lsn is after the - * checkpoint snapshot lsn taken. Redo records that are prior snapshot are - * ignored. - * @return Boolean value denoting success or failure. - */ - bool ApplyRedoLog(uint64_t redoLsn, char* data, size_t len) override; - /** - * @brief performs a commit on an in-process transaction, - * @return Boolean value denoting success or failure to commit. - */ - bool CommitRecoveredTransaction(uint64_t externalTransactionId) override; - - void SetCsn(uint64_t csn) override; - - /** - * @brief adds the surrogate array inside surrogate to the surrogate list - */ - void AddSurrogateArrayToList(SurrogateState& surrogate) override; - - bool IsErrorSet() const override - { - return m_errorSet; - } - - inline void SetLastReplayLsn(uint64_t replayLsn) override - { - if (m_lastReplayLsn < replayLsn) { - m_lastReplayLsn = replayLsn; - } - } - - inline uint64_t GetLastReplayLsn() const override - { - return m_lastReplayLsn; - } - - /** - * @class LogStats - * @brief A per-table recovery stats collector - */ - class LogStats { - public: - struct Entry { - explicit Entry(uint64_t tableId) : m_inserts(0), m_updates(0), m_deletes(0), m_id(tableId) - {} - - void IncInsert() - { - ++m_inserts; - } - - void IncUpdate() - { - ++m_updates; - } - - void IncDelete() - { - ++m_deletes; - } - - std::atomic m_inserts; - - std::atomic m_updates; - - std::atomic m_deletes; - - uint64_t m_id; - }; - - LogStats() : m_commits(0), m_numEntries(0) - {} - - ~LogStats() - { - for (std::vector::iterator it = m_tableStats.begin(); it != m_tableStats.end(); ++it) { - if (*it) { - delete *it; - } - } - } - - void IncInsert(uint64_t id) - { - uint64_t idx = 0; - if (FindIdx(id, idx)) { - m_tableStats[idx]->IncInsert(); - } - } - - void IncUpdate(uint64_t id) - { - uint64_t idx = 0; - if (FindIdx(id, idx)) { - m_tableStats[idx]->IncUpdate(); - } - } - - void IncDelete(uint64_t id) - { - uint64_t idx = 0; - if (FindIdx(id, idx)) { - m_tableStats[idx]->IncDelete(); - } - } - - inline void IncCommit() - { - ++m_commits; - } - - /** - * @brief Prints the stats data to the log - */ - void Print(); - - private: - /** - * @brief Returns a table id array index. it will create - * a new table entry if necessary. - * @param tableId The id of the table. - * @param id The returned array index. - * @return Boolean value denoting success or failure. - */ - bool FindIdx(uint64_t tableId, uint64_t& id); - - std::map m_idToIdx; - - std::vector m_tableStats; - - std::atomic m_commits; - - spin_lock m_slock; - - uint64_t m_numEntries; - }; - - LogStats* m_logStats; - - std::map m_preCommitedTables; - -private: - static constexpr uint32_t NUM_REDO_RECOVERY_THREADS = 1; - - /** - * @brief performs a redo on a segment, which is either a recovery op - * or a segment that belongs to a 2pc recovered transaction. - * @param segment the segment to redo. - * @param csn the segment's csn - * @param transactionId the transaction id of the segment - * @param rState the operation to perform on the segment. - * @return RC value denoting the operation's status - */ - RC RedoSegment(LogSegment* segment, uint64_t csn, uint64_t transactionId, RecoveryOps::RecoveryOpState rState); - - /** - * @brief inserts a segment in to the in-process transactions map - * @param segment the segment to redo. - * @return Boolean value denoting success or failure. - */ - bool InsertLogSegment(LogSegment* segment); - - /** - * @brief performs an operation on a recovered transaction. - * @param internalTransactionId the internal transaction id to operate on. - * @param externalTransactionId the external transaction id to operate on. - * @param rState the transaction id of the segment - * @return Boolean value denoting success or failure. - */ - bool OperateOnRecoveredTransaction( - uint64_t internalTransactionId, uint64_t externalTransactionId, RecoveryOps::RecoveryOpState rState); - - /** - * @brief checks if a transaction id specified in the log segment is an mot - * only one. - * @param segment the log segment to check. - * @return Boolean value that is true if the id is an mot one. - */ - static bool IsMotTransactionId(LogSegment* segment); - - /** - * @brief checks the clog if a transaction id is in commit state. - * @param xid the transaction id. - * @return Boolean value that is true if the transaction is committed. - */ - bool IsTransactionIdCommitted(uint64_t xid); - - /** - * @brief returns if a checkpoint is valid by its id. - * @param id the checkpoint's id. - * @return Boolean value that is true if the transaction is committed. - */ - bool IsCheckpointValid(uint64_t id); - - /** - * @brief returns if a recovery memory limit reached. - * @return Boolean value that is true if there is not enough memory for - * recovery. - */ - bool IsRecoveryMemoryLimitReached(uint32_t numThreads); - - /** - * @brief restores the surrogate counters to their last good known state - */ - void ApplySurrogate(); - - bool m_initialized; - - bool m_recoverFromCkptDone; - - uint64_t m_lsn; - - uint64_t m_lastReplayLsn; - - std::atomic m_tid; - - std::atomic m_maxRecoveredCsn; - - bool m_enableLogStats; - - SurrogateState m_surrogateState; - - std::mutex m_surrogateListLock; - - std::list m_surrogateList; - - bool m_errorSet; - - CommitLogStatusCallback m_clogCallback; - - int m_threadId; - - SurrogateState m_sState; - - uint16_t m_maxConnections; - - CheckpointRecovery m_checkpointRecovery; -}; -} // namespace MOT - -#endif /* RECOVERY_MANAGER_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager_factory.h b/src/gausskernel/storage/mot/core/system/recovery/recovery_manager_factory.h index 0d71fb7f7..b4a0a5253 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/recovery_manager_factory.h +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_manager_factory.h @@ -25,14 +25,23 @@ #ifndef RECOVERY_MANAGER_FACTORY_H #define RECOVERY_MANAGER_FACTORY_H -#include "recovery_manager.h" +#include "recovery_mode.h" +#include "mtls_recovery_manager.h" namespace MOT { class RecoveryManagerFactory { public: static IRecoveryManager* CreateRecoveryManager() { - return new (std::nothrow) RecoveryManager(); + IRecoveryManager* recoveryManager = nullptr; + switch (GetGlobalConfiguration().m_recoveryMode) { + case RecoveryMode::MTLS: + recoveryManager = new (std::nothrow) MTLSRecoveryManager(); + break; + default: + break; + } + return recoveryManager; } private: diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.cpp b/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.cpp new file mode 100644 index 000000000..99d1de9e0 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * recovery_mode.cpp + * Recovery mode types. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/recovery_mode.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "recovery_mode.h" +#include "utilities.h" + +#include + +namespace MOT { +DECLARE_LOGGER(RecoveryMode, Recovery) + +static const char* RECOVERY_MTLS_STR = "mtls"; +static const char* RECOVERY_INVALID_STR = "INVALID"; + +static const char* recoveryModeNames[] = {RECOVERY_MTLS_STR, RECOVERY_INVALID_STR}; + +RecoveryMode RecoveryModeFromString(const char* recoveryMode) +{ + RecoveryMode handlerType = RecoveryMode::RECOVERY_INVALID; + + if (strcmp(recoveryMode, RECOVERY_MTLS_STR) == 0) { + handlerType = RecoveryMode::MTLS; + } else { + MOT_LOG_ERROR("Invalid recovery mode: %s", recoveryMode); + } + + return handlerType; +} + +extern const char* RecoveryModeToString(const RecoveryMode& recoveryMode) +{ + if (recoveryMode < RecoveryMode::RECOVERY_INVALID) { + return recoveryModeNames[(uint32_t)recoveryMode]; + } else { + return RECOVERY_INVALID_STR; + } +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.h b/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.h new file mode 100644 index 000000000..8a57d1e4a --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_mode.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * recovery_mode.h + * Recovery mode types. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/recovery_mode.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef RECOVERY_MODE_H +#define RECOVERY_MODE_H + +#include +#include "type_formatter.h" + +namespace MOT { +enum class RecoveryMode : uint32_t { + /** @var Use mtls (low footprint) multiple threads. */ + MTLS, + + /** @var Constant value designating invalid recovery mode (indicates error in configuration loading). */ + RECOVERY_INVALID +}; + +/** + * @brief Converts a redo log handler type string to enumeration value. + * @param redoLogHandlerType The redo log handler type string. + * @return The redo log handler enumeration value. + */ +extern RecoveryMode RecoveryModeFromString(const char* recoveryMode); + +/** + * @brief Converts a redo log handler type enumeration value to string. + * @param redoLogHandlerType The redo log handler type enumeration. + * @return The redo log handler type string. + */ +extern const char* RecoveryModeToString(const RecoveryMode& recoveryMode); + +/** + * @class TypeFormatter + * @brief Specialization of TypeFormatter with [ T = RecoveryMode ]. + */ +template <> +class TypeFormatter { +public: + /** + * @brief Converts a value to string. + * @param value The value to convert. + * @param[out] stringValue The resulting string. + */ + static inline const char* ToString(const RecoveryMode& value, mot_string& stringValue) + { + stringValue = RecoveryModeToString(value); + return stringValue.c_str(); + } + + /** + * @brief Converts a string to a value. + * @param The string to convert. + * @param[out] The resulting value. + * @return Boolean value denoting whether the conversion succeeded or not. + */ + static inline bool FromString(const char* stringValue, RecoveryMode& value) + { + value = RecoveryModeFromString(stringValue); + return value != RecoveryMode::RECOVERY_INVALID; + } +}; +} // namespace MOT + +#endif /* RECOVERY_MODE_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.cpp b/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.cpp index 69038a968..16493bae8 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.cpp +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.cpp @@ -21,60 +21,69 @@ * * ------------------------------------------------------------------------- */ - +#include "recovery_ops.h" #include "mot_engine.h" #include "serializable.h" -#include "recovery_manager.h" #include "checkpoint_utils.h" #include "bitmapset.h" #include "column.h" #include "txn_insert_action.h" - #include #include namespace MOT { DECLARE_LOGGER(RecoveryOps, Recovery); -uint32_t RecoveryOps::RecoverLogOperation(TxnManager* txn, uint8_t* data, uint64_t csn, uint64_t transactionId, - uint32_t tid, SurrogateState& sState, RC& status, bool& wasCommit) +uint32_t RecoveryOps::RecoverLogOperation( + IRecoveryOpsContext* ctx, uint8_t* data, uint64_t transactionId, uint32_t tid, SurrogateState& sState, RC& status) { - if (txn == nullptr) { + if (ctx == nullptr || ctx->GetTxn() == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "%s: invalid TxnManager object", __FUNCTION__); status = RC_ERROR; return 0; } + status = RC_OK; OperationCode opCode = *static_cast((void*)data); + MOT_LOG_DEBUG("cur opcode %s", OperationCodeToString(opCode)); switch (opCode) { case CREATE_ROW: - return RecoverLogOperationInsert(txn, data, csn, tid, sState, status); + return RecoverLogOperationInsert(ctx, data, tid, sState, status); case UPDATE_ROW: - return RecoverLogOperationUpdate(txn, data, csn, tid, status); - case OVERWRITE_ROW: - return RecoverLogOperationOverwrite(txn, data, csn, tid, sState, status); + return RecoverLogOperationUpdate(ctx, data, tid, status); case REMOVE_ROW: - return RecoverLogOperationDelete(txn, data, csn, tid, status); + return RecoverLogOperationDelete(ctx, data, tid, status); case CREATE_TABLE: - return RecoverLogOperationCreateTable(txn, data, status, COMMIT, transactionId); + return RecoverLogOperationCreateTable(ctx, data, status, transactionId); case CREATE_INDEX: - return RecoverLogOperationCreateIndex(txn, data, tid, status, COMMIT); + return RecoverLogOperationCreateIndex(ctx, data, tid, status); case DROP_TABLE: - return RecoverLogOperationDropTable(txn, data, status, COMMIT); + return RecoverLogOperationDropTable(ctx, data, status); case DROP_INDEX: - return RecoverLogOperationDropIndex(txn, data, status, COMMIT); + return RecoverLogOperationDropIndex(ctx, data, status); case TRUNCATE_TABLE: - return RecoverLogOperationTruncateTable(txn, data, status, COMMIT); + return RecoverLogOperationTruncateTable(ctx, data, status); + case ALTER_TABLE_ADD_COLUMN: + return RecoverLogOperationAlterTableAddColumn(ctx, data, status); + case ALTER_TABLE_DROP_COLUMN: + return RecoverLogOperationAlterTableDropColumn(ctx, data, status); + case ALTER_TABLE_RENAME_COLUMN: + return RecoverLogOperationAlterTableRenameColumn(ctx, data, status); + case COMMIT_TX_1VCC: case COMMIT_TX: + case COMMIT_DDL_TX: + case COMMIT_CROSS_TX: case COMMIT_PREPARED_TX: - wasCommit = true; - return RecoverLogOperationCommit(txn, data, csn, tid, status); + return RecoverLogOperationCommit(ctx, data, tid, status); + case PARTIAL_REDO_TX_1VCC: case PARTIAL_REDO_TX: + case PARTIAL_REDO_DDL_TX: + case PARTIAL_REDO_CROSS_TX: case PREPARE_TX: return sizeof(EndSegmentBlock); case ROLLBACK_TX: case ROLLBACK_PREPARED_TX: - return RecoverLogOperationRollback(txn, data, csn, tid, status); + return RecoverLogOperationRollback(ctx, data, tid, status); default: MOT_LOG_ERROR("Unknown recovery redo record op-code: %u", (unsigned)opCode); status = RC_ERROR; @@ -83,7 +92,7 @@ uint32_t RecoveryOps::RecoverLogOperation(TxnManager* txn, uint8_t* data, uint64 } uint32_t RecoveryOps::RecoverLogOperationCreateTable( - TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state, uint64_t transactionId) + IRecoveryOpsContext* ctx, uint8_t* data, RC& status, uint64_t transactionId) { if (GetGlobalConfiguration().m_enableIncrementalCheckpoint) { status = RC_ERROR; @@ -92,152 +101,43 @@ uint32_t RecoveryOps::RecoverLogOperationCreateTable( return 0; } - uint32_t tableId; - uint64_t extId; - uint32_t metaVersion; - std::string tableName; - std::string longName; - TableInfo* tableInfo = nullptr; - std::map::iterator it; OperationCode opCode = *(OperationCode*)data; - MOT_ASSERT(opCode == CREATE_TABLE); data += sizeof(OperationCode); size_t bufSize = 0; Extract(data, bufSize); - Table* table = nullptr; - - MOT_LOG_DEBUG("RecoverLogOperationCreateTable: state %u", state); - - switch (state) { - case COMMIT: - MOT_LOG_DEBUG("RecoverLogOperationCreateTable: COMMIT"); - CreateTable(txn, (char*)data, status, table, TRANSACTIONAL); - break; - - case TPC_APPLY: - CreateTable(txn, (char*)data, status, table, DONT_ADD_TO_ENGINE); - if (status == RC_OK && table != nullptr) { - tableInfo = new (std::nothrow) TableInfo(table, transactionId); - if (tableInfo != nullptr) { - ((RecoveryManager*)GetRecoveryManager())->m_preCommitedTables[table->GetTableId()] = tableInfo; - } else { - status = RC_ERROR; - MOT_LOG_ERROR("RecoverLogOperationCreateTable: failed to create table info"); - } - } - if (status != RC_OK) { - if (table != nullptr) - delete table; - if (tableInfo != nullptr) - delete tableInfo; - } - break; - - case TPC_COMMIT: - case TPC_ABORT: - Table::DeserializeNameAndIds((const char*)data, metaVersion, tableId, extId, tableName, longName); - it = ((RecoveryManager*)GetRecoveryManager())->m_preCommitedTables.find(tableId); - if (it != ((RecoveryManager*)GetRecoveryManager())->m_preCommitedTables.end()) { - tableInfo = (TableInfo*)it->second; - if (tableInfo != nullptr) { - if (state == TPC_COMMIT) { - MOT_LOG_DEBUG("RecoverLogOperationCreateTable - adding table %s to engine", longName.c_str()); - status = GetTableManager()->AddTable(tableInfo->m_table) ? RC_OK : RC_ERROR; - } else - status = RC_OK; - } else { - MOT_LOG_ERROR("RecoverLogOperationCreateTable: no data on table info"); - status = RC_ERROR; - } - if (tableInfo != nullptr && tableInfo->m_table != nullptr && state == TPC_ABORT) - delete tableInfo->m_table; - if (tableInfo != nullptr) - delete tableInfo; - ((RecoveryManager*)GetRecoveryManager())->m_preCommitedTables.erase(it); - } else { - MOT_LOG_ERROR( - "RecoverLogOperationCreateTable: could not find table [%lu] %s", tableId, tableName.c_str()); - status = RC_ERROR; - } - break; - - default: - MOT_LOG_ERROR("RecoverLogOperationCreateTable: bad state"); - status = RC_ERROR; - break; - } - + CreateTable(ctx, (char*)data, status, TRANSACTIONAL); return sizeof(OperationCode) + sizeof(bufSize) + bufSize; } -uint32_t RecoveryOps::RecoverLogOperationDropTable(TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state) +uint32_t RecoveryOps::RecoverLogOperationDropTable(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) { OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == DROP_TABLE); data += sizeof(OperationCode); - switch (state) { - case COMMIT: - case TPC_COMMIT: - DropTable(txn, (char*)data, status); - break; - case ABORT: - case TPC_APPLY: - case TPC_ABORT: - break; - default: - MOT_LOG_ERROR("RecoverLogOperationDropTable: bad state"); - status = RC_ERROR; - break; - } - return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(uint64_t); + DropTable(ctx, (char*)data, status); + return sizeof(OperationCode) + sizeof(uint64_t) + sizeof(uint32_t); } -uint32_t RecoveryOps::RecoverLogOperationCreateIndex( - TxnManager* txn, uint8_t* data, uint32_t tid, RC& status, RecoveryOpState state) +uint32_t RecoveryOps::RecoverLogOperationCreateIndex(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status) { OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == CREATE_INDEX); data += sizeof(OperationCode); size_t bufSize = 0; Extract(data, bufSize); - switch (state) { - case COMMIT: - case TPC_COMMIT: - CreateIndex(txn, (char*)data, tid, status); - break; - case ABORT: - case TPC_APPLY: - case TPC_ABORT: - break; - default: - MOT_LOG_ERROR("RecoverLogOperationCreateIndex: bad state"); - status = RC_ERROR; - break; - } - return sizeof(OperationCode) + sizeof(bufSize) + sizeof(uint32_t) + sizeof(uint64_t) + bufSize; + CreateIndex(ctx, (char*)data, tid, status); + return sizeof(OperationCode) + sizeof(bufSize) + sizeof(uint64_t) + sizeof(uint32_t) + + bufSize; // sizeof(uint64_t) is for tableId } -uint32_t RecoveryOps::RecoverLogOperationDropIndex(TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state) +uint32_t RecoveryOps::RecoverLogOperationDropIndex(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) { OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == DROP_INDEX); data += sizeof(OperationCode); char* extracted = (char*)data; - switch (state) { - case COMMIT: - case TPC_COMMIT: - DropIndex(txn, extracted, status); - break; - case ABORT: - case TPC_APPLY: - case TPC_ABORT: - break; - default: - MOT_LOG_ERROR("RecoverLogOperationDropIndex: bad state"); - status = RC_ERROR; - break; - } + DropIndex(ctx, extracted, status); uint64_t tableId = 0; size_t nameLen = 0; uint32_t metaVersion; @@ -247,38 +147,56 @@ uint32_t RecoveryOps::RecoverLogOperationDropIndex(TxnManager* txn, uint8_t* dat return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(uint64_t) + sizeof(size_t) + nameLen; } -uint32_t RecoveryOps::RecoverLogOperationTruncateTable( - TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state) +uint32_t RecoveryOps::RecoverLogOperationTruncateTable(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) { OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == TRUNCATE_TABLE); data += sizeof(OperationCode); - switch (state) { - case COMMIT: - case TPC_COMMIT: - TruncateTable(txn, (char*)data, status); - break; - case ABORT: - case TPC_APPLY: - case TPC_ABORT: - break; - default: - MOT_LOG_ERROR("RecoverLogOperationTruncateTable: bad state"); - status = RC_ERROR; - break; - } - return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(uint64_t); + TruncateTable(ctx, (char*)data, status); + return sizeof(OperationCode) + sizeof(uint64_t) + sizeof(uint32_t); +} + +uint32_t RecoveryOps::RecoverLogOperationAlterTableAddColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) +{ + OperationCode opCode = *(OperationCode*)data; + MOT_ASSERT(opCode == ALTER_TABLE_ADD_COLUMN); + data += sizeof(OperationCode); + size_t bufSize = 0; + Extract(data, bufSize); + AlterTableAddColumn(ctx, (char*)data, status); + return sizeof(OperationCode) + sizeof(bufSize) + sizeof(uint64_t) + sizeof(uint32_t) + bufSize; +} + +uint32_t RecoveryOps::RecoverLogOperationAlterTableDropColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) +{ + OperationCode opCode = *(OperationCode*)data; + MOT_ASSERT(opCode == ALTER_TABLE_DROP_COLUMN); + data += sizeof(OperationCode); + size_t bufSize = 0; + Extract(data, bufSize); + AlterTableDropColumn(ctx, (char*)data, status); + return sizeof(OperationCode) + sizeof(bufSize) + sizeof(uint64_t) + sizeof(uint32_t) + bufSize; +} + +uint32_t RecoveryOps::RecoverLogOperationAlterTableRenameColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status) +{ + OperationCode opCode = *(OperationCode*)data; + MOT_ASSERT(opCode == ALTER_TABLE_RENAME_COLUMN); + data += sizeof(OperationCode); + size_t bufSize = 0; + Extract(data, bufSize); + AlterTableRenameColumn(ctx, (char*)data, status); + return sizeof(OperationCode) + sizeof(bufSize) + sizeof(uint64_t) + sizeof(uint32_t) + bufSize; } uint32_t RecoveryOps::RecoverLogOperationInsert( - TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status) + IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, SurrogateState& sState, RC& status) { - uint64_t tableId, rowLength, exId; + uint64_t tableId, rowLength, exId, rowId; uint32_t metaVersion; uint16_t keyLength; uint8_t* keyData = nullptr; uint8_t* rowData = nullptr; - uint64_t row_id; OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == CREATE_ROW); @@ -294,32 +212,110 @@ uint32_t RecoveryOps::RecoverLogOperationInsert( } Extract(data, tableId); Extract(data, exId); - Extract(data, row_id); + Extract(data, rowId); Extract(data, keyLength); keyData = ExtractPtr(data, keyLength); Extract(data, rowLength); rowData = ExtractPtr(data, rowLength); - InsertRow( - txn, tableId, exId, (char*)keyData, keyLength, (char*)rowData, rowLength, csn, tid, sState, status, row_id); - if (((RecoveryManager*)GetRecoveryManager())->m_logStats != nullptr) - ((RecoveryManager*)GetRecoveryManager())->m_logStats->IncInsert(tableId); - return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(tableId) + sizeof(exId) + sizeof(row_id) + + InsertRow(ctx, tableId, exId, (char*)keyData, keyLength, (char*)rowData, rowLength, tid, sState, status, rowId); + GetRecoveryManager()->LogInsert(tableId); + return sizeof(OperationCode) + sizeof(metaVersion) + sizeof(tableId) + sizeof(exId) + sizeof(rowId) + sizeof(keyLength) + keyLength + sizeof(rowLength) + rowLength; } -uint32_t RecoveryOps::RecoverLogOperationUpdate(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status) +RC RecoveryOps::LookupRow( + IRecoveryOpsContext* ctx, Table* table, Key* key, const AccessType type, uint64_t prev_version, bool isMvccUpgrade) { - uint64_t tableId; + RC rc = RC_OK; + TxnManager* txn = ctx->GetTxn(); + const char* opType = (type == WR) ? "Update" : "Delete"; + + do { + Row* row = txn->RowLookupByKey(table, RD, key, rc); + if (rc == RC_MEMORY_ALLOCATION_ERROR) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Recovery Manager Lookup Row", + "%s: Initial row lookup failed due to lack of memory", + opType); + return RC_MEMORY_ALLOCATION_ERROR; + } + + if (row == nullptr && !ctx->ShouldRetryOp()) { + // Row not found. Error!!! Got an update for non existing row. + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Lookup Row", + "%s: Row not found, key: %s, tableId: (%lu:%u)", + opType, + key->GetKeyStr().c_str(), + table->GetTableExId(), + table->GetTableId()); + return RC_ERROR; + } + + if (row == nullptr) { + (void)usleep(1000); + continue; + } + + PrimarySentinel* ps = row->GetPrimarySentinel(); + row = ps->GetData(); + if (isMvccUpgrade || (row && ps->GetTransactionId() == prev_version)) { + // The correct version is in store, can continue. + row = txn->RowLookupByKey(table, type, key, rc); + if (rc == RC_MEMORY_ALLOCATION_ERROR) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Recovery Manager Lookup Row", + "%s: Row lookup after version check failed due to lack of memory", + opType); + return RC_MEMORY_ALLOCATION_ERROR; + } + break; + } + + // Wrong version in store, if retry is not supported (synchronous recovery) + // an error occurred. Cannot recover operation + if (!ctx->ShouldRetryOp()) { + row = ps->GetData(); + if (ps->GetTransactionId() == prev_version) { + row = txn->RowLookupByKey(table, type, key, rc); + if (rc == RC_MEMORY_ALLOCATION_ERROR) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Recovery Manager Lookup Row", + "%s: Retried row lookup failed due to lack of memory"); + return RC_MEMORY_ALLOCATION_ERROR; + } + break; + } else { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Lookup Row", + "%s: TID version incorrect, tableId: (%lu:%u), expected version %lu, found version %lu", + table->GetTableExId(), + table->GetTableId(), + prev_version, + ps->GetTransactionId()); + return RC_ERROR; + } + } + + // If we got here, we need to retry and wait till the right version will be committed to store. + (void)usleep(1000); + } while (true); + + return RC_OK; +} + +uint32_t RecoveryOps::RecoverLogOperationUpdate(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status) +{ + uint64_t tableId, exId, prev_version; uint32_t metaVersion; - uint64_t exId; uint16_t keyLength; uint8_t *keyData, *rowData; - uint64_t version; - - status = RC_OK; - + bool idxUpd = false; + TxnManager* txn = ctx->GetTxn(); OperationCode opCode = *(OperationCode*)data; MOT_ASSERT(opCode == UPDATE_ROW); + uint32_t retSize = sizeof(OperationCode) + sizeof(metaVersion) + sizeof(tableId) + sizeof(exId) + + sizeof(keyLength) + sizeof(prev_version); data += sizeof(OperationCode); Extract(data, metaVersion); @@ -330,11 +326,18 @@ uint32_t RecoveryOps::RecoverLogOperationUpdate(TxnManager* txn, uint8_t* data, status = RC_ERROR; return 0; } + + bool isMvccUpgrade = (metaVersion < MetadataProtoVersion::METADATA_VER_MVCC) ? true : false; + Extract(data, tableId); Extract(data, exId); Extract(data, keyLength); keyData = ExtractPtr(data, keyLength); - Extract(data, version); + Extract(data, prev_version); + if (metaVersion >= MetadataProtoVersion::METADATA_VER_IDX_COL_UPD) { + Extract(data, idxUpd); + retSize += sizeof(idxUpd); + } Table* table = txn->GetTableByExternalId(exId); if (table == nullptr) { @@ -358,48 +361,61 @@ uint32_t RecoveryOps::RecoverLogOperationUpdate(TxnManager* txn, uint8_t* data, Index* index = table->GetPrimaryIndex(); Key* key = txn->GetTxnKey(index); if (key == nullptr) { - status = RC_ERROR; + status = RC_MEMORY_ALLOCATION_ERROR; MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Update Row", "failed to allocate key"); return 0; } key->CpKey((const uint8_t*)keyData, keyLength); - Row* row = txn->RowLookupByKey(table, RD_FOR_UPDATE, key); - if (row == nullptr) { - // Row not found. Error!!! Got an update for non existing row. + + status = LookupRow(ctx, table, key, MOT::AccessType::WR, prev_version, isMvccUpgrade); + if (status != RC_OK) { txn->DestroyTxnKey(key); MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recovery Manager Update Row", - "row not found, key: %s, tableId: %lu", - key->GetKeyStr().c_str(), - tableId); - status = RC_ERROR; + "Failed to lookup row: %s (%d)", + RcToString(status), + status); return 0; } - if (row->GetCommitSequenceNumber() > csn) { - // Row CSN is newer. Error!!! + status = txn->UpdateLastRowState(MOT::AccessType::WR); + if (status != RC_OK) { txn->DestroyTxnKey(key); MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recovery Manager Update Row", - "row CSN is newer! %lu > %lu, key: %s, tableId: %lu", - row->GetCommitSequenceNumber(), - csn, - key->GetKeyStr().c_str(), - tableId); - status = RC_ERROR; + "Failed to update last row state: %s (%d)", + RcToString(status), + status); return 0; } + Row* localRow = txn->GetLastAccessedDraft(); + uint16_t num_columns = table->GetFieldCount() - 1; - rowData = const_cast(row->GetData()); + rowData = const_cast(localRow->GetData()); BitmapSet row_valid_columns(rowData + table->GetFieldOffset((uint64_t)0), num_columns); BitmapSet updated_columns(ExtractPtr(data, BitmapSet::GetLength(num_columns)), num_columns); BitmapSet valid_columns(ExtractPtr(data, BitmapSet::GetLength(num_columns)), num_columns); BitmapSet::BitmapSetIterator updated_columns_it(updated_columns); BitmapSet::BitmapSetIterator valid_columns_it(valid_columns); + UpdateIndexColumnType idxUpdType = idxUpd ? UPDATE_COLUMN_SECONDARY : UPDATE_COLUMN_NONE; + MOT::TxnIxColUpdate colUpd(table, txn, updated_columns.GetData(), idxUpdType); uint64_t size = 0; errno_t erc; + // generate old keys in case of indexed column update + if (unlikely(idxUpd)) { + status = colUpd.InitAndBuildOldKeys(localRow); + if (status != RC_OK) { + txn->DestroyTxnKey(key); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Update Row - Index Column Update", + "Failed to init old keys with error: %s (%d)", + RcToString(status), + status); + return 0; + } + } while (!updated_columns_it.End()) { if (updated_columns_it.IsSet()) { if (valid_columns_it.IsSet()) { @@ -413,54 +429,41 @@ uint32_t RecoveryOps::RecoverLogOperationUpdate(TxnManager* txn, uint8_t* data, row_valid_columns.UnsetBit(updated_columns_it.GetPosition()); } } - valid_columns_it.Next(); - updated_columns_it.Next(); + (void)valid_columns_it.Next(); + (void)updated_columns_it.Next(); } - txn->UpdateLastRowState(MOT::AccessType::WR); + if (unlikely(idxUpd)) { + status = colUpd.FilterColumnUpdate(localRow); + if (status != RC_OK) { + txn->DestroyTxnKey(key); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Update Row - Index Column Update", + "FilterColumnUpdate failed: %s (%d)", + RcToString(status), + status); + return 0; + } + + status = txn->UpdateRow(updated_columns, &colUpd); + if (status != RC_OK) { + txn->DestroyTxnKey(key); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Update Row - Index Column Update", + "Failed to update last row state: %s (%d)", + RcToString(status), + status); + return 0; + } + } txn->DestroyTxnKey(key); - if (((RecoveryManager*)GetRecoveryManager())->m_logStats != nullptr) - ((RecoveryManager*)GetRecoveryManager())->m_logStats->IncUpdate(tableId); - return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(tableId) + sizeof(exId) + sizeof(keyLength) + keyLength + - sizeof(version) + updated_columns.GetLength() + valid_columns.GetLength() + size; + GetRecoveryManager()->LogUpdate(tableId); + + retSize += keyLength + updated_columns.GetLength() + valid_columns.GetLength() + size; + return retSize; } -uint32_t RecoveryOps::RecoverLogOperationOverwrite( - TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status) -{ - uint64_t tableId, rowLength, exId; - uint32_t metaVersion; - uint16_t keyLength; - uint8_t* keyData = nullptr; - uint8_t* rowData = nullptr; - - OperationCode opCode = *(OperationCode*)data; - MOT_ASSERT(opCode == OVERWRITE_ROW); - data += sizeof(OperationCode); - - Extract(data, metaVersion); - if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { - MOT_LOG_ERROR("OverwriteRow: metadata version %u is greater than current %u", - metaVersion, - MetadataProtoVersion::METADATA_VER_CURR); - status = RC_ERROR; - return 0; - } - Extract(data, tableId); - Extract(data, exId); - Extract(data, keyLength); - keyData = ExtractPtr(data, keyLength); - Extract(data, rowLength); - rowData = ExtractPtr(data, rowLength); - - UpdateRow(txn, tableId, exId, (char*)keyData, keyLength, (char*)rowData, rowLength, csn, tid, sState, status); - if (((RecoveryManager*)GetRecoveryManager())->m_logStats != nullptr) - ((RecoveryManager*)GetRecoveryManager())->m_logStats->IncUpdate(tableId); - return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(tableId) + sizeof(exId) + sizeof(keyLength) + keyLength + - sizeof(rowLength) + rowLength; -} - -uint32_t RecoveryOps::RecoverLogOperationDelete(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status) +uint32_t RecoveryOps::RecoverLogOperationDelete(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status) { uint64_t tableId, exId, version; uint32_t metaVersion; @@ -479,50 +482,41 @@ uint32_t RecoveryOps::RecoverLogOperationDelete(TxnManager* txn, uint8_t* data, status = RC_ERROR; return 0; } + + bool isMvccUpgrade = (metaVersion < MetadataProtoVersion::METADATA_VER_MVCC) ? true : false; + Extract(data, tableId); Extract(data, exId); Extract(data, keyLength); keyData = ExtractPtr(data, keyLength); Extract(data, version); - - DeleteRow(txn, tableId, exId, (char*)keyData, keyLength, csn, tid, status); - if (((RecoveryManager*)GetRecoveryManager())->m_logStats != nullptr) - ((RecoveryManager*)GetRecoveryManager())->m_logStats->IncDelete(tableId); + DeleteRow(ctx, tableId, exId, (char*)keyData, keyLength, version, tid, isMvccUpgrade, status); + GetRecoveryManager()->LogDelete(tableId); return sizeof(OperationCode) + sizeof(uint32_t) + sizeof(tableId) + sizeof(exId) + sizeof(keyLength) + keyLength + sizeof(version); } -uint32_t RecoveryOps::RecoverLogOperationCommit(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status) +uint32_t RecoveryOps::RecoverLogOperationCommit(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status) { - // OperationCode + CSN + transaction_type + commit_counter + transaction_id - if (((RecoveryManager*)GetRecoveryManager())->m_logStats != nullptr) - ((RecoveryManager*)GetRecoveryManager())->m_logStats->IncCommit(); - status = CommitTransaction(txn, csn); - if (status != RC_OK) { - MOT_LOG_ERROR("Failed to commit row recovery from log: %s (error code: %d)", RcToString(status), (int)status); - } + GetRecoveryManager()->LogCommit(); return sizeof(EndSegmentBlock); } -uint32_t RecoveryOps::RecoverLogOperationRollback( - TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status) +uint32_t RecoveryOps::RecoverLogOperationRollback(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status) { - // OperationCode + CSN + transaction_type + commit_counter + transaction_id - RecoveryOps::RollbackTransaction(txn); return sizeof(EndSegmentBlock); } -void RecoveryOps::InsertRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - char* rowData, uint64_t rowLen, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId, - bool insertLocked) +void RecoveryOps::InsertRow(IRecoveryOpsContext* ctx, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, + char* rowData, uint64_t rowLen, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId) { + TxnManager* txn = ctx->GetTxn(); Table* table = txn->GetTableByExternalId(exId); if (table == nullptr) { status = RC_ERROR; MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recover Insert Row", "Table %" PRIu64 " does not exist", exId); return; } - Row* row = table->CreateNewRow(); if (row == nullptr) { status = RC_ERROR; @@ -530,72 +524,61 @@ void RecoveryOps::InsertRow(TxnManager* txn, uint64_t tableId, uint64_t exId, ch return; } row->CopyData((const uint8_t*)rowData, rowLen); - row->SetCommitSequenceNumber(csn); row->SetRowId(rowId); - if (insertLocked == true) { - row->SetTwoPhaseMode(true); - row->m_rowHeader.Lock(); - } - - Key* key = nullptr; MOT::Index* ix = nullptr; - MOT::Key* cleanupKeys[table->GetNumIndexes()] = {nullptr}; ix = table->GetPrimaryIndex(); - key = txn->GetTxnKey(ix); - if (key == nullptr) { - table->DestroyRow(row); - status = RC_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recover Insert Row", "Failed to create primary key"); - return; - } - cleanupKeys[0] = key; if (ix->IsFakePrimary()) { - row->SetSurrogateKey(*(uint64_t*)keyData); - sState.UpdateMaxKey(rowId); - } - key->CpKey((const uint8_t*)keyData, keyLen); - txn->GetNextInsertItem()->SetItem(row, ix, key); - for (uint16_t i = 1; i < table->GetNumIndexes(); i++) { - ix = table->GetSecondaryIndex(i); - key = txn->GetTxnKey(ix); - if (key == nullptr) { - status = RC_MEMORY_ALLOCATION_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Recover Insert Row", - "Failed to create key for secondary index %s", - ix->GetName().c_str()); - for (uint16_t j = 0; j < table->GetNumIndexes(); j++) { - if (cleanupKeys[j] != nullptr) { - txn->DestroyTxnKey(cleanupKeys[j]); - } - } + row->SetSurrogateKey(); + if (!sState.UpdateMaxKey(rowId)) { + status = RC_ERROR; + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Recovery Manager Insert Row", "Failed to update surrogate state"); table->DestroyRow(row); - txn->Rollback(); return; } - cleanupKeys[i] = key; - ix->BuildKey(table, row, key); - txn->GetNextInsertItem()->SetItem(row, ix, key); } - status = txn->InsertRow(row); + InsItem* insItem = txn->GetNextInsertItem(); + if (insItem == nullptr) { + status = RC_MEMORY_ALLOCATION_ERROR; + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recovery Manager Insert Row", "Failed to get insert item"); + table->DestroyRow(row); + return; + } + insItem->SetItem(row, ix); + for (uint16_t i = 1; i < table->GetNumIndexes(); i++) { + ix = table->GetSecondaryIndex(i); + insItem = txn->GetNextInsertItem(); + if (insItem == nullptr) { + status = RC_MEMORY_ALLOCATION_ERROR; + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recovery Manager Insert Row", "Failed to get insert item"); + table->DestroyRow(row); + return; + } + insItem->SetItem(row, ix); + } - if (insertLocked == true) { - row->GetPrimarySentinel()->Lock(0); - } + do { + status = txn->InsertRecoveredRow(row); + if (status == RC_OK) { + break; + } else { + if (ctx->ShouldRetryOp()) { + (void)usleep(1000); + } else { + break; + } + } + } while (true); if (status != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recovery Manager Insert Row", "failed to insert row (status %u)", status); } } -void RecoveryOps::DeleteRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - uint64_t csn, uint32_t tid, RC& status) +void RecoveryOps::DeleteRow(IRecoveryOpsContext* ctx, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, + uint64_t prev_version, uint32_t tid, bool isMvccUpgrade, RC& status) { - Row* row = nullptr; - Key* key = nullptr; - Index* index = nullptr; - + TxnManager* txn = ctx->GetTxn(); Table* table = txn->GetTableByExternalId(exId); if (table == nullptr) { MOT_REPORT_ERROR( @@ -604,110 +587,51 @@ void RecoveryOps::DeleteRow(TxnManager* txn, uint64_t tableId, uint64_t exId, ch return; } - index = table->GetPrimaryIndex(); - key = txn->GetTxnKey(index); + Index* index = table->GetPrimaryIndex(); + Key* key = txn->GetTxnKey(index); if (key == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Delete Row", "failed to create key"); status = RC_ERROR; return; } - key->CpKey((const uint8_t*)keyData, keyLen); - row = txn->RowLookupByKey(table, WR, key); - if (row != nullptr) { - status = txn->DeleteLastRow(); - if (status != RC_OK) { - if (MOT_IS_OOM()) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Recovery Manager Delete Row", "failed to remove row due to lack of memory"); - status = RC_MEMORY_ALLOCATION_ERROR; - } else { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Recovery Manager Delete Row", - "failed to remove row: %s (error code: %d)", - RcToString(status), - status); - } - } - } else { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recovery Manager Delete Row", "getData failed"); - status = RC_ERROR; - } - txn->DestroyTxnKey(key); - status = RC_OK; -} - -void RecoveryOps::UpdateRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - char* rowData, uint64_t rowLen, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status) -{ - Table* table = txn->GetTableByExternalId(exId); - if (table == nullptr) { - status = RC_ERROR; - MOT_REPORT_ERROR( - MOT_ERROR_INVALID_ARG, "Recovery Manager Update Row", "table %" PRIu64 " does not exist", exId); - return; - } - - uint64_t tableExId = table->GetTableExId(); - if (tableExId != exId) { - status = RC_ERROR; - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Recovery Manager Update Row", "exId mismatch: my %lu - pkt %lu", tableExId, exId); - return; - } - - Index* index = table->GetPrimaryIndex(); - Key* key = txn->GetTxnKey(index); - if (key == nullptr) { - status = RC_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Update Row", "failed to create key"); - return; - } key->CpKey((const uint8_t*)keyData, keyLen); - Row* row = txn->RowLookupByKey(table, RD_FOR_UPDATE, key); - if (row == nullptr) { - // Row not found. Error!!! Got an update for non existing row. + + status = LookupRow(ctx, table, key, MOT::AccessType::DEL, prev_version, isMvccUpgrade); + if (status != RC_OK) { txn->DestroyTxnKey(key); - status = RC_ERROR; - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "updateRow", "row not found, key: %s, tableId: %lu", key->GetKeyStr().c_str(), tableId); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Delete Row", + "Failed to lookup row: %s (%d)", + RcToString(status), + status); return; - } else { - // CSNs can be equal if updated during the same transaction - if (row->GetCommitSequenceNumber() <= csn) { - row->CopyData((const uint8_t*)rowData, rowLen); - row->SetCommitSequenceNumber(csn); - if (row->IsAbsentRow()) { - row->UnsetAbsentRow(); - } - txn->UpdateLastRowState(MOT::AccessType::WR); - } else { - // Row CSN is newer. Error!!! - txn->DestroyTxnKey(key); - status = RC_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "updateRow", - "row CSN is newer! %lu > %lu, key: %s, tableId: %lu", - row->GetCommitSequenceNumber(), - csn, - key->GetKeyStr().c_str(), - tableId); - return; - } + } + + status = txn->DeleteLastRow(); + if (status != RC_OK) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Recovery Manager Delete Row", + "Failed to remove row: %s (%d)", + RcToString(status), + status); } txn->DestroyTxnKey(key); } -void RecoveryOps::CreateTable(TxnManager* txn, char* data, RC& status, Table*& table, CreateTableMethod method) +void RecoveryOps::CreateTable(IRecoveryOpsContext* ctx, char* data, RC& status, CreateTableMethod method) { /* first verify that the table does not exists */ string name; string longName; uint32_t intId = 0; uint64_t extId = 0; + TxnManager* txn = ctx->GetTxn(); uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; Table::DeserializeNameAndIds((const char*)data, metaVersion, intId, extId, name, longName); - MOT_LOG_DEBUG("CreateTable: got intId: %u, extId: %lu, %s/%s", intId, extId, name.c_str(), longName.c_str()); + + MOT_LOG_TRACE("Recover Create Table: Creating %s/%s (%lu:%u)", name.c_str(), longName.c_str(), extId, intId); + if (GetTableManager()->VerifyTableExists(intId, extId, name, longName)) { MOT_LOG_DEBUG("CreateTable: table %u already exists", intId); return; @@ -720,10 +644,11 @@ void RecoveryOps::CreateTable(TxnManager* txn, char* data, RC& status, Table*& t status = RC_ERROR; return; } - table = new (std::nothrow) Table(); + + Table* table = new (std::nothrow) Table(); if (table == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Recovery Manager Create Table", "failed to allocate table object"); - status = RC_ERROR; + status = RC_MEMORY_ALLOCATION_ERROR; return; } @@ -774,12 +699,13 @@ void RecoveryOps::CreateTable(TxnManager* txn, char* data, RC& status, Table*& t return; } -void RecoveryOps::DropTable(TxnManager* txn, char* data, RC& status) +void RecoveryOps::DropTable(IRecoveryOpsContext* ctx, char* data, RC& status) { - char* in = (char*)data; + char* in = data; uint64_t externalTableId; uint32_t metaVersion; string tableName; + TxnManager* txn = ctx->GetTxn(); in = SerializablePOD::Deserialize(in, metaVersion); if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { @@ -796,7 +722,10 @@ void RecoveryOps::DropTable(TxnManager* txn, char* data, RC& status) /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ return; } - tableName.assign(table->GetLongTableName()); + (void)tableName.assign(table->GetLongTableName()); + + MOT_LOG_TRACE("Recover Drop Table: Dropping %s (%" PRIu64 ")", tableName.c_str(), externalTableId); + status = txn->DropTable(table); if (status != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, @@ -805,15 +734,15 @@ void RecoveryOps::DropTable(TxnManager* txn, char* data, RC& status) tableName.c_str(), externalTableId); } - MOT_LOG_DEBUG("DropTable: table %s [%" PRIu64 "] dropped", tableName.c_str(), externalTableId); } -void RecoveryOps::CreateIndex(TxnManager* txn, char* data, uint32_t tid, RC& status) +void RecoveryOps::CreateIndex(IRecoveryOpsContext* ctx, char* data, uint32_t tid, RC& status) { - char* in = (char*)data; + char* in = data; uint64_t externalTableId; uint32_t metaVersion; Table::CommonIndexMeta idx; + TxnManager* txn = ctx->GetTxn(); in = SerializablePOD::Deserialize(in, metaVersion); if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { @@ -831,14 +760,20 @@ void RecoveryOps::CreateIndex(TxnManager* txn, char* data, uint32_t tid, RC& sta status = RC_ERROR; return; } + in = table->DeserializeMeta(in, idx, metaVersion); bool primary = idx.m_indexOrder == IndexOrder::INDEX_ORDER_PRIMARY; - MOT_LOG_DEBUG("createIndex: creating %s Index, %s %lu", + + MOT_LOG_TRACE("Recover Create Index: Creating %s Index, %s (%lu) of table %s (%lu:%u)", primary ? "Primary" : "Secondary", idx.m_name.c_str(), - idx.m_indexExtId); + idx.m_indexExtId, + table->GetTableName().c_str(), + table->GetTableExId(), + table->GetTableId()); + Index* index = nullptr; - status = table->CreateIndexFromMeta(idx, primary, tid, false, &index); + status = table->CreateIndexFromMeta(idx, primary, tid, metaVersion, false, &index); if (status != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recover Create Index", @@ -847,7 +782,9 @@ void RecoveryOps::CreateIndex(TxnManager* txn, char* data, uint32_t tid, RC& sta RcToString(status), status); } else { + table->GetOrigTable()->WrLock(); status = txn->CreateIndex(table, index, primary); + table->GetOrigTable()->Unlock(); if (status != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Recover Create Index", @@ -859,13 +796,14 @@ void RecoveryOps::CreateIndex(TxnManager* txn, char* data, uint32_t tid, RC& sta } } -void RecoveryOps::DropIndex(TxnManager* txn, char* data, RC& status) +void RecoveryOps::DropIndex(IRecoveryOpsContext* ctx, char* data, RC& status) { - RC res; - char* in = (char*)data; + RC res = RC_OK; + char* in = data; uint64_t externalTableId; uint32_t metaVersion; string indexName; + TxnManager* txn = ctx->GetTxn(); in = SerializablePOD::Deserialize(in, metaVersion); if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { @@ -879,18 +817,25 @@ void RecoveryOps::DropIndex(TxnManager* txn, char* data, RC& status) Table* table = txn->GetTableByExternalId(externalTableId); if (table == nullptr) { /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ - MOT_LOG_DEBUG("dropIndex: could not find table %" PRIu64, externalTableId); + MOT_LOG_DEBUG("Recover Drop Index: could not find table %" PRIu64, externalTableId); return; } in = SerializableSTR::Deserialize(in, indexName); + + MOT_LOG_TRACE("Recover Drop Index: Dropping %s of table %s (%lu:%u)", + indexName.c_str(), + table->GetTableName().c_str(), + table->GetTableExId(), + table->GetTableId()); + Index* index = table->GetSecondaryIndex(indexName); if (index == nullptr) { res = RC_INDEX_NOT_FOUND; } else { - table->WrLock(); + table->GetOrigTable()->WrLock(); res = txn->DropIndex(index); - table->Unlock(); + table->GetOrigTable()->Unlock(); } if (res != RC_OK) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, @@ -901,11 +846,12 @@ void RecoveryOps::DropIndex(TxnManager* txn, char* data, RC& status) } } -void RecoveryOps::TruncateTable(TxnManager* txn, char* data, RC& status) +void RecoveryOps::TruncateTable(IRecoveryOpsContext* ctx, char* data, RC& status) { - char* in = (char*)data; + char* in = data; uint64_t externalTableId; uint32_t metaVersion; + TxnManager* txn = ctx->GetTxn(); in = SerializablePOD::Deserialize(in, metaVersion); if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { @@ -919,46 +865,192 @@ void RecoveryOps::TruncateTable(TxnManager* txn, char* data, RC& status) Table* table = txn->GetTableByExternalId(externalTableId); if (table == nullptr) { /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ - MOT_LOG_DEBUG("truncateTable: could not find table %" PRIu64, externalTableId); + MOT_LOG_DEBUG("Recover Truncate: could not find table %" PRIu64, externalTableId); return; } - table->WrLock(); + table->GetOrigTable()->WrLock(); status = txn->TruncateTable(table); - table->Unlock(); + table->GetOrigTable()->Unlock(); } -RC RecoveryOps::BeginTransaction(TxnManager* txn, uint64_t replayLsn) +void RecoveryOps::AlterTableAddColumn(IRecoveryOpsContext* ctx, char* data, RC& status) { - if (txn == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "%s: invalid TxnManager object", __FUNCTION__); - return RC_ERROR; + char* in = data; + uint64_t externalTableId; + uint32_t metaVersion; + Table::CommonColumnMeta col; + Column* newColumn = nullptr; + size_t defSize = 0; + uintptr_t defValue = 0; + bool freeDefValue = false; + TxnManager* txn = ctx->GetTxn(); + + in = SerializablePOD::Deserialize(in, metaVersion); + if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { + MOT_LOG_ERROR("Recover Add Column: metadata version %u is greater than current %u", + metaVersion, + MetadataProtoVersion::METADATA_VER_CURR); + status = RC_ERROR; + return; } + in = SerializablePOD::Deserialize(in, externalTableId); + Table* table = txn->GetTableByExternalId(externalTableId); + if (table == nullptr) { + /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ + MOT_LOG_DEBUG("Recover Add Column: could not find table %" PRIu64, externalTableId); + return; + } + in = table->DeserializeMeta(in, col, metaVersion); + if (col.m_hasDefault) { + switch (col.m_type) { + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_BLOB: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TIMETZ: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_TINTERVAL: + case MOT_CATALOG_FIELD_TYPES::MOT_TYPE_INTERVAL: + SerializableCharBuf::DeserializeSize(in, defSize); + if (defSize > 0) { + defValue = (uintptr_t)malloc(defSize); + if (defValue == 0) { + MOT_LOG_ERROR( + "Recover Add Column: failed to allocate memory for default value of column %s", col.m_name); + status = MOT::RC_MEMORY_ALLOCATION_ERROR; + return; + } + in = SerializableCharBuf::Deserialize(in, (char*)defValue, defSize); + freeDefValue = true; + } + break; + default: + in = SerializablePOD::Deserialize(in, defValue); + defSize = col.m_size; + break; + } + } + table->GetOrigTable()->WrLock(); + status = table->CreateColumn(newColumn, + col.m_name, + col.m_size, + col.m_type, + col.m_isNotNull, + col.m_envelopeType, + col.m_hasDefault, + defValue, + defSize); + if (status == RC_OK && newColumn != nullptr) { + status = txn->AlterTableAddColumn(table, newColumn); + } + table->GetOrigTable()->Unlock(); + + if (freeDefValue && defValue != 0) { + free((void*)defValue); + } + if (status != RC_OK) { + if (newColumn != nullptr) { + delete newColumn; + } + } +} + +void RecoveryOps::AlterTableDropColumn(IRecoveryOpsContext* ctx, char* data, RC& status) +{ + char* in = data; + uint64_t externalTableId; + uint32_t metaVersion; + char colName[Column::MAX_COLUMN_NAME_LEN]; + TxnManager* txn = ctx->GetTxn(); + + in = SerializablePOD::Deserialize(in, metaVersion); + if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { + MOT_LOG_ERROR("Recover Drop Column: metadata version %u is greater than current %u", + metaVersion, + MetadataProtoVersion::METADATA_VER_CURR); + status = RC_ERROR; + return; + } + in = SerializablePOD::Deserialize(in, externalTableId); + Table* table = txn->GetTableByExternalId(externalTableId); + if (table == nullptr) { + /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ + MOT_LOG_DEBUG("Recover Drop Column: could not find table %" PRIu64, externalTableId); + return; + } + in = SerializableARR::Deserialize(in, colName); + uint64_t colId = table->GetFieldId(colName); + if (colId == (uint64_t)-1) { + MOT_LOG_ERROR("Recover Drop Column: Column %s not found in table %s", colName, table->GetTableName().c_str()); + status = RC_ERROR; + return; + } + Column* col = table->GetField(colId); + table->GetOrigTable()->WrLock(); + status = txn->AlterTableDropColumn(table, col); + table->GetOrigTable()->Unlock(); +} + +void RecoveryOps::AlterTableRenameColumn(IRecoveryOpsContext* ctx, char* data, RC& status) +{ + char* in = data; + uint64_t externalTableId; + uint32_t metaVersion; + char oldName[Column::MAX_COLUMN_NAME_LEN]; + char newName[Column::MAX_COLUMN_NAME_LEN]; + TxnManager* txn = ctx->GetTxn(); + + in = SerializablePOD::Deserialize(in, metaVersion); + if (metaVersion > MetadataProtoVersion::METADATA_VER_CURR) { + MOT_LOG_ERROR("Recover Rename Column: metadata version %u is greater than current %u", + metaVersion, + MetadataProtoVersion::METADATA_VER_CURR); + status = RC_ERROR; + return; + } + in = SerializablePOD::Deserialize(in, externalTableId); + Table* table = txn->GetTableByExternalId(externalTableId); + if (table == nullptr) { + /* this might happen if we try to replay an outdated xlog entry - currently we do not error out */ + MOT_LOG_DEBUG("Recover Rename Column: could not find table %" PRIu64, externalTableId); + return; + } + in = SerializableARR::Deserialize(in, oldName); + in = SerializableARR::Deserialize(in, newName); + uint64_t colId = table->GetFieldId(oldName); + if (colId == (uint64_t)-1) { + MOT_LOG_ERROR("Recover Rename Column: Column %s not found in table %s", oldName, table->GetTableName().c_str()); + status = RC_ERROR; + return; + } + uint16_t len = strlen(newName); + if (len >= MOT::Column::MAX_COLUMN_NAME_LEN) { + MOT_LOG_ERROR("Recover Rename Column: Column name %s is longer %u than allowed %u", + newName, + len, + MOT::Column::MAX_COLUMN_NAME_LEN); + status = RC_ERROR; + return; + } + Column* col = table->GetField(colId); + table->GetOrigTable()->WrLock(); + status = txn->AlterTableRenameColumn(table, col, newName); + table->GetOrigTable()->Unlock(); +} + +RC RecoveryOps::BeginTransaction(IRecoveryOpsContext* ctx, uint64_t replayLsn) +{ + RC rc = RC_OK; + TxnManager* txn = ctx->GetTxn(); txn->StartTransaction(INVALID_TRANSACTION_ID, READ_COMMITED); txn->SetReplayLsn(replayLsn); - return RC_OK; -} - -RC RecoveryOps::CommitTransaction(TxnManager* txn, uint64_t csn) -{ - txn->SetCommitSequenceNumber(csn); - RC status = txn->Commit(); - if (status != RC_OK) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Recover DB", - "Failed to commit recovery transaction: %s (error code: %u)", - RcToString(status), - status); - txn->Rollback(); - } else { - txn->EndTransaction(); + rc = txn->GcSessionStart(); + if (rc != RC_OK) { + return rc; } - - return status; -} - -void RecoveryOps::RollbackTransaction(TxnManager* txn) -{ - txn->Rollback(); + txn->GcSessionEnd(); + txn->m_dummyIndex->ClearKeyCache(); + txn->m_accessMgr->ClearTableCache(); + txn->m_accessMgr->ClearDummyTableCache(); + return rc; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.h b/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.h index ca684a9b2..964fec451 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.h +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_ops.h @@ -30,6 +30,7 @@ #include "txn.h" #include "global.h" #include "surrogate_state.h" +#include "irecovery_ops_context.h" namespace MOT { class RecoveryOps { @@ -45,10 +46,12 @@ public: {} ~TableInfo() - {} + { + m_table = nullptr; + } + private: Table* m_table; - uint64_t m_transactionId; }; @@ -78,259 +81,256 @@ public: return outptr; } - enum RecoveryOpState { COMMIT = 1, ABORT = 2, TPC_APPLY = 3, TPC_COMMIT = 4, TPC_ABORT = 5 }; - enum CreateTableMethod { TRANSACTIONAL = 1, ADD_TO_ENGINE = 2, DONT_ADD_TO_ENGINE = 3 }; /** * @brief performs a recovery operation on a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn the operations's csn. * @param transactionId the transaction id * @param tid the thread id of the recovering thread * @param sState the returned surrogate state of this operation * @param[out] status the returned status of the operation - * @param[out] Was this a commit operation (required for managing transactional state). * @return Int value denoting the number of bytes recovered */ - static uint32_t RecoverLogOperation(TxnManager* txn, uint8_t* data, uint64_t csn, uint64_t transactionId, - uint32_t tid, SurrogateState& sState, RC& status, bool& wasCommit); + static uint32_t RecoverLogOperation(IRecoveryOpsContext* ctx, uint8_t* data, uint64_t transactionId, uint32_t tid, + SurrogateState& sState, RC& status); /** * @brief Starts a new transaction for recovery operations. - * @param transaction manager object. + * @param recovery operation context. * @param replayLsn the redo LSN for this transaction during replay. */ - static RC BeginTransaction(TxnManager* txn, uint64_t replayLsn = 0); + static RC BeginTransaction(IRecoveryOpsContext* ctx, uint64_t replayLsn = 0); private: /** * @brief performs an insert operation of a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn the operations's csn. * @param tid the thread id of the recovering thread. * @param sState the returned surrogate state of this operation. * @param status the returned status of the operation. * @return Int value denoting the number of bytes recovered. */ static uint32_t RecoverLogOperationInsert( - TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status); + IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, SurrogateState& sState, RC& status); /** * @brief performs a delta update operation of a data buffer. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn the operations's csn. * @param tid the thread id of the recovering thread. * @param status the returned status of the operation. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationUpdate(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status); - - /** - * @brief performs an update operation of a data buffer. - * @param transaction manager object. - * @param data the buffer to recover. - * @param csn the operations's csn. - * @param tid the thread id of the recovering thread. - * @param sState the returned surrogate state of this operation. - * @param status the returned status of the operation.. - * @return Int value denoting the number of bytes recovered.. - */ - static uint32_t RecoverLogOperationOverwrite( - TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status); + static uint32_t RecoverLogOperationUpdate(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status); /** * @brief performs a delete operation of a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn the operations's csn. * @param tid the thread id of the recovering thread. * @param status the returned status of the operation. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationDelete(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status); + static uint32_t RecoverLogOperationDelete(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status); /** * @brief performs a commit operation of a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn the operations's csn. * @param tid the thread id of the recovering thread. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationCommit(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status); + static uint32_t RecoverLogOperationCommit(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status); /** * @brief performs a rollback operation of a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. - * @param csn The CSN of the operation. * @param tid the thread id of the recovering thread. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationRollback(TxnManager* txn, uint8_t* data, uint64_t csn, uint32_t tid, RC& status); + static uint32_t RecoverLogOperationRollback(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status); /** * @brief performs a create table operation from a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. * @param status the returned status of the operation - * @param state the operation's state. * @param transactionId the transaction id of the operation. * @return Int value denoting the number of bytes recovered. */ static uint32_t RecoverLogOperationCreateTable( - TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state, uint64_t transactionId); + IRecoveryOpsContext* ctx, uint8_t* data, RC& status, uint64_t transactionId); /** * @brief performs a drop table operation from a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. * @param status the returned status of the operation - * @param state the operation's state. * @return Int value denoting the number of bytes recovered */ - static uint32_t RecoverLogOperationDropTable(TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state); + static uint32_t RecoverLogOperationDropTable(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); /** * @brief performs a create index operation from a data buffer. + * @param recovery operation context. * @param data the buffer to recover. * @param tid the thread id of the recovering thread. * @param status the returned status of the operation. - * @param state the operation's state. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationCreateIndex( - TxnManager* txn, uint8_t* data, uint32_t tid, RC& status, RecoveryOpState state); + static uint32_t RecoverLogOperationCreateIndex(IRecoveryOpsContext* ctx, uint8_t* data, uint32_t tid, RC& status); /** * @brief performs a drop index operation from a data buffer. - * @param transaction manager object. + * @param recovery operation context. * @param data the buffer to recover. * @param status the returned status of the operation. - * @param state the operation's state. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationDropIndex(TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state); + static uint32_t RecoverLogOperationDropIndex(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); /** * @brief performs a truncate table operation from a data buffer. - * @param transaction manager object. + * @param recovery operation context.. + * @param data the buffer to recover. + * @param status the returned status of the operation. + * @return Int value denoting the number of bytes recovered. + */ + static uint32_t RecoverLogOperationTruncateTable(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); + + /** + * @brief performs an alter table add column operation from a data buffer. + * @param recovery operation context. + * @param data the buffer to recover. + * @param status the returned status of the operation. + * @return Int value denoting the number of bytes recovered. + */ + static uint32_t RecoverLogOperationAlterTableAddColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); + + /** + * @brief performs an alter table drop column operation from a data buffer. + * @param recovery operation context. * @param data the buffer to recover. * @param status the returned status of the operation. * @param state the operation's state. * @return Int value denoting the number of bytes recovered. */ - static uint32_t RecoverLogOperationTruncateTable(TxnManager* txn, uint8_t* data, RC& status, RecoveryOpState state); + static uint32_t RecoverLogOperationAlterTableDropColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); + + /** + * @brief performs an alter table rename column operation from a data buffer. + * @param recovery operation context. + * @param data the buffer to recover. + * @param status the returned status of the operation. + * @param state the operation's state. + * @return Int value denoting the number of bytes recovered. + */ + static uint32_t RecoverLogOperationAlterTableRenameColumn(IRecoveryOpsContext* ctx, uint8_t* data, RC& status); /** * @brief performs the actual row deletion from the storage. - * @param transaction manager object. + * @param recovery operation context. * @param tableId the table's id. * @param exId the the table's external id. * @param keyData key's data buffer. * @param keyLen key's data buffer len. - * @param csn the operations's csn. + * @param version of the deleted row in store. * @param tid the thread id of the recovering thread. + * @param isMvccUpgrade indicates if this record is from a pre mvcc version. * @param status the returned status of the operation */ - static void DeleteRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - uint64_t csn, uint32_t tid, RC& status); + static void DeleteRow(IRecoveryOpsContext* ctx, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, + uint64_t prev_version, uint32_t tid, bool isMvccUpgrade, RC& status); + + static RC LookupRow(IRecoveryOpsContext* ctx, Table* table, Key* key, const AccessType type, uint64_t prev_version, + bool isMvccUpgrade); /** * @brief performs the actual row insertion to the storage. - * @param transaction manager object. + * @param recovery operation context. * @param tableId the table's id. * @param exId the table's external id. * @param keyData key's data buffer. * @param keyLen key's data buffer len. * @param rowData row's data buffer. * @param rowLen row's data buffer len. - * @param csn the operations's csn. * @param tid the thread id of the recovering thread. * @param sState the returned surrugate state. * @param status the returned status of the operation * @param rowId the row's internal id - * @param insertLocked should this row be inserted locked */ - static void InsertRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - char* rowData, uint64_t rowLen, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId, - bool insertLocked = false); - - /** - * @brief performs the actual row update in the storage. - * @param transaction manager object. - * @param tableId the table's id. - * @param exId the the table's external id. - * @param keyData key's data buffer. - * @param keyLen key's data buffer len. - * @param rowData row's data buffer. - * @param rowLen row's data buffer len. - * @param csn the operations's csn. - * @param tid the thread id of the recovering thread. - * @param sState the returned surrugate state. - * @param status the returned status of the operation. - */ - static void UpdateRow(TxnManager* txn, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, - char* rowData, uint64_t rowLen, uint64_t csn, uint32_t tid, SurrogateState& sState, RC& status); + static void InsertRow(IRecoveryOpsContext* ctx, uint64_t tableId, uint64_t exId, char* keyData, uint16_t keyLen, + char* rowData, uint64_t rowLen, uint32_t tid, SurrogateState& sState, RC& status, uint64_t rowId); /** * @brief performs the actual table creation. - * @param transaction manager object. + * @param recovery operation context. * @param data the table's data. * @param status the returned status of the operation. - * @param table the returned table object. * @param method controls whether the table is added to the engine or not. */ - static void CreateTable(TxnManager* txn, char* data, RC& status, Table*& table, CreateTableMethod method); + static void CreateTable(IRecoveryOpsContext* ctx, char* data, RC& status, CreateTableMethod method); /** * @brief performs the actual table deletion. - * @param transaction manager object. + * @param recovery operation context. * @param data the table's data. * @param status the returned status of the operation. */ - static void DropTable(TxnManager* txn, char* data, RC& status); + static void DropTable(IRecoveryOpsContext* ctx, char* data, RC& status); /** * @brief performs the actual index creation. - * @param transaction manager object. + * @param recovery operation context. * @param data the table's data * @param the thread identifier * @param status the returned status of the operation */ - static void CreateIndex(TxnManager* txn, char* data, uint32_t tid, RC& status); + static void CreateIndex(IRecoveryOpsContext* ctx, char* data, uint32_t tid, RC& status); /** * @brief performs the actual index deletion. - * @param transaction manager object. + * @param recovery operation context. * @param data the table's data. * @param status the returned status of the operation. */ - static void DropIndex(TxnManager* txn, char* data, RC& status); + static void DropIndex(IRecoveryOpsContext* ctx, char* data, RC& status); /** * @brief performs the actual table truncation. + * @param recovery operation context. + * @param data the table's data. + * @param status the returned status of the operation. + */ + static void TruncateTable(IRecoveryOpsContext* ctx, char* data, RC& status); + + /** + * @brief performs the actual alter table add column. * @param transaction manager object. * @param data the table's data. * @param status the returned status of the operation. */ - static void TruncateTable(TxnManager* txn, char* data, RC& status); + static void AlterTableAddColumn(IRecoveryOpsContext* ctx, char* data, RC& status); /** - * @brief Commits the current recovery transaction. + * @brief performs the actual alter table drop column. * @param transaction manager object. - * @param transaction's commit sequence number. + * @param data the table's data. + * @param status the returned status of the operation. */ - static RC CommitTransaction(TxnManager* txn, uint64_t csn); + static void AlterTableDropColumn(IRecoveryOpsContext* ctx, char* data, RC& status); /** - * @brief Rolls back the current recovery transaction. + * @brief performs the actual alter table rename column. * @param transaction manager object. + * @param data the table's data. + * @param status the returned status of the operation. */ - static void RollbackTransaction(TxnManager* txn); + static void AlterTableRenameColumn(IRecoveryOpsContext* ctx, char* data, RC& status); }; // class RecoveryOps } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/recovery_stats.h b/src/gausskernel/storage/mot/core/system/recovery/recovery_stats.h new file mode 100644 index 000000000..ee05e6cb2 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/recovery_stats.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * recovery_stats.h + * Table recovery stats collector. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/recovery_stats.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef RECOVERY_STATS_H +#define RECOVERY_STATS_H + +#include +#include +#include + +namespace MOT { +/** + * @class RecoveryStats + * @brief Recovery statistics collector + */ +class RecoveryStats { +public: + /** + * @struct Entry + * @brief A per-table entry to collect recovery statistics. + */ + struct TableEntry { + explicit TableEntry(uint64_t tableId) : m_inserts(0), m_updates(0), m_deletes(0), m_id(tableId) + {} + + void IncInsert() + { + (void)++m_inserts; + } + + void IncUpdate() + { + (void)++m_updates; + } + + void IncDelete() + { + (void)++m_deletes; + } + + std::atomic m_inserts; + + std::atomic m_updates; + + std::atomic m_deletes; + + uint64_t m_id; + }; + + RecoveryStats() : m_commits(0), m_numEntries(0) + {} + + ~RecoveryStats() + { + Clear(); + } + + void IncInsert(uint64_t id) + { + uint32_t idx = 0; + if (FindIdx(id, idx)) { + m_tableStats[idx]->IncInsert(); + } + } + + void IncUpdate(uint64_t id) + { + uint32_t idx = 0; + if (FindIdx(id, idx)) { + m_tableStats[idx]->IncUpdate(); + } + } + + void IncDelete(uint64_t id) + { + uint32_t idx = 0; + if (FindIdx(id, idx)) { + m_tableStats[idx]->IncDelete(); + } + } + + inline void IncCommit() + { + (void)++m_commits; + } + + /** + * @brief Prints the stats data to the log + */ + void Print() + { + MOT_LOG_INFO("\n>> Log Recovery Stats >>"); + for (uint32_t i = 0; i < m_numEntries; i++) { + MOT_LOG_INFO("TableId %lu, Inserts: %lu, Updates: %lu, Deletes: %lu", + m_tableStats[i]->m_id, + m_tableStats[i]->m_inserts.load(), + m_tableStats[i]->m_updates.load(), + m_tableStats[i]->m_deletes.load()); + } + MOT_LOG_INFO("Overall commit ops: %lu\n", m_commits.load()); + } + + /** + * @brief Clears all the statistics. + */ + void Clear() + { + m_idToIdx.clear(); + for (std::vector::iterator it = m_tableStats.begin(); it != m_tableStats.end(); (void)++it) { + if (*it) { + delete *it; + } + } + m_tableStats.clear(); + m_numEntries = 0; + m_commits = 0; + } + +private: + /** + * @brief Returns a table id array index. it will create + * a new table entry if necessary. + * @param tableId The id of the table. + * @param id The returned array index. + * @return Boolean value denoting success or failure. + */ + bool FindIdx(uint64_t tableId, uint32_t& idx) + { + TableIdToStatsIdxMap::iterator it; + std::lock_guard lock(m_slock); + idx = m_numEntries; + it = m_idToIdx.find(tableId); + if (it == m_idToIdx.end()) { + TableEntry* newEntry = new (std::nothrow) TableEntry(tableId); + if (newEntry == nullptr) { + return false; + } + m_tableStats.push_back(newEntry); + (void)m_idToIdx.insert(std::pair(tableId, m_numEntries)); + m_numEntries++; + } else { + idx = it->second; + } + return true; + } + + using TableIdToStatsIdxMap = std::map; + + TableIdToStatsIdxMap m_idToIdx; + + std::vector m_tableStats; + + std::atomic m_commits; + + spin_lock m_slock; + + uint32_t m_numEntries; + + DECLARE_CLASS_LOGGER(); +}; +} // namespace MOT + +#endif /* RECOVERY_STATS_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.cpp b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.cpp index 4915a99ef..13692bcb7 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.cpp +++ b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.cpp @@ -25,11 +25,6 @@ #include "redo_log_transaction_iterator.h" namespace MOT { -bool RedoLogTransactionIterator::End() -{ - return (m_position >= m_bufferLength); -} - bool RedoLogTransactionIterator::Next() { m_position += m_txnLength; @@ -51,14 +46,21 @@ void* RedoLogTransactionIterator::GetTransactionEntry() return reinterpret_cast(m_buffer + m_position); } -LogSegment* RedoLogTransactionIterator::AllocRedoSegment(uint64_t replayLsn) +LogSegment* RedoLogTransactionIterator::AllocRedoSegment(uint64_t replayLsn, SPSCVarSizeAllocator* allocator) { LogSegment* segment = new (std::nothrow) LogSegment(); if (segment == nullptr) { return nullptr; } segment->m_len = m_txnLength - sizeof(uint32_t); - segment->m_data = new (std::nothrow) char[segment->m_len]; + if (allocator == nullptr) { + segment->m_data = new (std::nothrow) char[segment->m_len]; + } else { + segment->m_data = (char*)(allocator->Alloc(segment->m_len * sizeof(char))); + if (segment->m_data != nullptr) { + segment->m_allocator = allocator; + } + } if (segment->m_data == nullptr) { delete segment; return nullptr; diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.h b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.h index deed6921f..1b0e5e65b 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.h +++ b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_iterator.h @@ -27,6 +27,7 @@ #include "redo_log_writer.h" #include "log_segment.h" +#include "spsc_allocator.h" namespace MOT { /** @@ -42,11 +43,16 @@ public: } ~RedoLogTransactionIterator() - {} + { + m_buffer = nullptr; + } bool Next(); - bool End(); + inline bool End() const + { + return (m_position >= m_bufferLength); + } inline uint64_t GetExternalTransactionId() const { @@ -73,9 +79,14 @@ public: return m_txnLength; } + inline const EndSegmentBlock& GetEndSegmentBlock() const + { + return m_endSegment; + } + void* GetTransactionEntry(); - LogSegment* AllocRedoSegment(uint64_t replayLsn); + LogSegment* AllocRedoSegment(uint64_t replayLsn, SPSCVarSizeAllocator* allocator = nullptr); private: char* m_buffer; diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.cpp b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.cpp new file mode 100644 index 000000000..cac99eb78 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * redo_log_transaction_player.cpp + * Implements a transaction replay mechanism. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "redo_log_transaction_player.h" +#include "mot_engine.h" + +namespace MOT { +DECLARE_LOGGER(RedologRecoveryPlayer, Recovery); + +RedoLogTransactionPlayer::RedoLogTransactionPlayer(IRecoveryManager* recoveryManager) + : m_inPool(false), + m_queueId(0), + m_transactionId(INVALID_TRANSACTION_ID), + m_externalId(INVALID_TRANSACTION_ID), + m_csn(INVALID_CSN), + m_replayLsn(0), + m_prevId(INVALID_TRANSACTION_ID), + m_numSegs(0), + m_processed(false), + m_firstSegment(false), + m_initialized(false), + m_retried(false), + m_recoveryManager(recoveryManager), + m_surrogateState(nullptr), + m_txn(nullptr) +{} + +RedoLogTransactionPlayer::~RedoLogTransactionPlayer() +{ + if (m_txn != nullptr) { + m_txn->Rollback(); + m_txn->~TxnManager(); + free(m_txn); + m_txn = nullptr; + } + m_recoveryManager = nullptr; + m_surrogateState = nullptr; + m_initialized = false; +} + +void RedoLogTransactionPlayer::Init(TxnManager* txn) +{ + m_txn = txn; + m_initialized = true; +} + +void RedoLogTransactionPlayer::InitRedoTransactionData( + uint64_t transactionId, uint64_t externalId, uint64_t csn, uint64_t replayLsn) +{ + m_transactionId = transactionId; + m_externalId = externalId; + m_csn = csn; + m_replayLsn = replayLsn; + m_retried = false; + m_txn->SetTransactionId(externalId); + m_txn->SetInternalTransactionId(transactionId); + m_txn->SetReplayLsn(replayLsn); + m_txn->SetCommitSequenceNumber(m_csn); + m_txn->SetVisibleCSN(static_cast(-1)); + m_firstSegment = false; + m_numSegs = 0; +} + +RC RedoLogTransactionPlayer::BeginTransaction() +{ + if (RecoveryOps::BeginTransaction(this, m_replayLsn) != RC_OK) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recover Redo Segment", "Cannot start a new transaction"); + return RC_ERROR; + } + return RC_OK; +} + +RC RedoLogTransactionPlayer::RedoSegment(LogSegment* segment) +{ + RC status = RC_OK; + uint8_t* endPosition = (uint8_t*)(segment->m_data + segment->m_len); + uint8_t* operationData = (uint8_t*)(segment->m_data); + while (operationData < endPosition) { + // redo log recovery - single threaded + if (IsRecoveryMemoryLimitReached(m_recoveryManager->GetNumRecoveryThreads())) { + status = RC_ERROR; + MOT_LOG_ERROR("Memory hard limit reached. Cannot recover datanode"); + break; + } + + operationData += RecoveryOps::RecoverLogOperation( + this, operationData, m_transactionId, MOTCurrThreadId, *m_surrogateState, status); + // check operation result status + if (status != RC_OK) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Recover Redo Segment", "Failed to recover redo segment"); + break; + } + } + IncNumSegs(); + return status; +} + +bool RedoLogTransactionPlayer::IsRecoveryMemoryLimitReached(uint32_t numThreads) const +{ + uint64_t memoryRequiredBytes = numThreads * MEM_CHUNK_SIZE_MB * MEGA_BYTE; + if (MOTEngine::GetInstance()->GetCurrentMemoryConsumptionBytes() + memoryRequiredBytes >= + MOTEngine::GetInstance()->GetHardMemoryLimitBytes()) { + MOT_LOG_WARN("IsRecoveryMemoryLimitReached: recovery memory limit reached " + "current memory: %lu, required memory: %lu, hard limit memory: %lu", + MOTEngine::GetInstance()->GetCurrentMemoryConsumptionBytes(), + memoryRequiredBytes, + MOTEngine::GetInstance()->GetHardMemoryLimitBytes()); + return true; + } + + return false; +} + +RC RedoLogTransactionPlayer::CommitTransaction() +{ + MOT_ASSERT(m_transactionId == m_txn->GetInternalTransactionId()); + m_txn->SetCommitSequenceNumber(m_csn); + m_txn->SetReplayLsn(m_replayLsn); + + RC status = m_txn->Commit(); + if (status != RC_OK) { + MOT_LOG_ERROR("Failed to commit recovery transaction: %s (error code: %u)", RcToString(status), status); + return status; + } else { + MOT_LOG_DEBUG("Committing transaction CSN = %lu", m_txn->GetCommitSequenceNumber()); + } + + MOTEngine::GetInstance()->GetCSNManager().SetCSN(m_csn); + m_txn->EndTransaction(); + return RC_OK; +} + +void RedoLogTransactionPlayer::CleanupTransaction() +{ + m_externalId = INVALID_TRANSACTION_ID; + m_csn = INVALID_CSN; + m_replayLsn = 0; + m_processed.store(false); + m_retried = false; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.h b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.h new file mode 100644 index 000000000..8b21a2223 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * redo_log_transaction_player.h + * Implements a transaction replay mechanism. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_player.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef REDOLOG_TRANSACTION_PLAYER_H +#define REDOLOG_TRANSACTION_PLAYER_H + +#include "global.h" +#include "txn.h" +#include "irecovery_manager.h" +#include "recovery_ops.h" +#include "surrogate_state.h" + +namespace MOT { +/** + * @brief Class for managing transaction replay mechanism. + */ +class RedoLogTransactionPlayer : public IRecoveryOpsContext { +public: + explicit RedoLogTransactionPlayer(IRecoveryManager* recoveryManager); + RedoLogTransactionPlayer(const RedoLogTransactionPlayer& orig) = delete; + RedoLogTransactionPlayer& operator=(const RedoLogTransactionPlayer& orig) = delete; + ~RedoLogTransactionPlayer() override; + + void Init(TxnManager* txn); + void InitRedoTransactionData(uint64_t transactionId, uint64_t externalId, uint64_t csn, uint64_t replayLsn); + RC BeginTransaction(); + RC CommitTransaction(); + void CleanupTransaction(); + + TxnManager* GetTxn() override + { + return m_txn; + } + + bool ShouldRetryOp() override + { + return m_recoveryManager->ShouldRetryOp(this); + } + + bool IsProcessed() const + { + return m_processed.load(); + } + + void MarkProcessed() + { + m_processed.store(true); + } + + void SetSurrogateState(SurrogateState* state) + { + m_surrogateState = state; + } + + inline bool IsRetried() const + { + return m_retried; + } + + inline void SetRetried() + { + m_retried = true; + } + + inline uint64_t GetTransactionId() const + { + return m_transactionId; + } + + inline uint64_t GetExternalId() const + { + return m_externalId; + } + + inline void SetReplayLSN(uint64_t lsn) + { + m_replayLsn = lsn; + m_txn->SetReplayLsn(lsn); + } + + inline uint64_t GetReplayLSN() const + { + return m_replayLsn; + } + + inline bool IsFirstSegment() const + { + return m_firstSegment; + } + + inline void SetFirstSegment(bool value) + { + m_firstSegment = value; + } + + inline uint64_t GetCSN() const + { + return m_csn; + } + + inline void SetPrevId(uint64_t id) + { + m_prevId = id; + } + + inline uint64_t GetPrevId() const + { + return m_prevId; + } + + inline void IncNumSegs() + { + m_numSegs++; + } + + inline uint64_t GetNumSegs() const + { + return m_numSegs; + } + + /** + * @brief performs a redo on a segment, which is either a recovery op + * or a segment that belongs to a 2pc recovered transaction. + * @param segment the segment to redo. + * @return RC value denoting the operation's status + */ + RC RedoSegment(LogSegment* segment); + + volatile bool m_inPool; + + uint32_t m_queueId; + +private: + bool IsRecoveryMemoryLimitReached(uint32_t numThreads) const; + + uint64_t m_transactionId; + uint64_t m_externalId; + uint64_t m_csn; + uint64_t m_replayLsn; + uint64_t m_prevId; + volatile uint64_t m_numSegs; + std::atomic m_processed; + volatile bool m_firstSegment; + bool m_initialized; + volatile bool m_retried = false; + IRecoveryManager* m_recoveryManager; + SurrogateState* m_surrogateState; + TxnManager* m_txn; +}; +} // namespace MOT + +#endif /* REDOLOG_TRANSACTION_PLAYER_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.cpp b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.cpp deleted file mode 100644 index ef8c1d546..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * redo_log_transaction_segments.cpp - * Implements an array of log segments that are part of a specific transaction id. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.cpp - * - * ------------------------------------------------------------------------- - */ - -#include "redo_log_transaction_segments.h" - -namespace MOT { -RedoLogTransactionSegments::~RedoLogTransactionSegments() -{ - if (m_segments != nullptr) { - for (uint32_t i = 0; i < m_count; i++) { - delete m_segments[i]; - } - free(m_segments); - } -} - -bool RedoLogTransactionSegments::Append(LogSegment* segment) -{ - if (m_count == m_maxSegments) { - // max segments allocated, need to extend the number of allocated LogSegments pointers - uint32_t newMaxSegments = m_maxSegments + DEFAULT_SEGMENT_NUM; - LogSegment** newSegments = (LogSegment**)malloc(newMaxSegments * sizeof(LogSegment*)); - if (newSegments != nullptr) { - if (m_segments != nullptr) { - errno_t erc = memcpy_s( - newSegments, newMaxSegments * sizeof(LogSegment*), m_segments, m_maxSegments * sizeof(LogSegment*)); - securec_check(erc, "\0", "\0"); - free(m_segments); - } - m_segments = newSegments; - m_maxSegments = newMaxSegments; - } else { - return false; - } - } - - m_size += segment->m_len; - m_segments[m_count] = segment; - m_count += 1; - return true; -} - -char* RedoLogTransactionSegments::GetData(size_t position, size_t length) const -{ - if (position + length > m_size) { - return nullptr; - } - - uint32_t currentEntry = 0; - while (currentEntry < m_count) { - if (position > m_segments[currentEntry]->m_len) { - position -= m_segments[currentEntry]->m_len; - currentEntry++; - } else { - if (position + length > m_segments[currentEntry]->m_len) { - // Cross segments is not supported for now - return nullptr; - } - return (m_segments[currentEntry]->m_data + position); - } - } - return nullptr; -} -} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.h b/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.h deleted file mode 100644 index f2cc0e3fd..000000000 --- a/src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * redo_log_transaction_segments.h - * Implements an array of log segments that are part of a specific transaction id. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/core/system/recovery/redo_log_transaction_segments.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef REDO_LOG_TRANSACTION_SEGMENTS_H -#define REDO_LOG_TRANSACTION_SEGMENTS_H - -#include "log_segment.h" - -namespace MOT { -class RedoLogTransactionSegments { -public: - explicit RedoLogTransactionSegments(TransactionId id) - : m_transactionId(id), m_segments(nullptr), m_count(0), m_size(0), m_maxSegments(0) - {} - - ~RedoLogTransactionSegments(); - - bool Append(LogSegment* segment); - - uint64_t GetTransactionId() const - { - return m_transactionId; - } - - size_t GetSize() const - { - return m_size; - } - - char* GetData(size_t position, size_t length) const; - - uint32_t GetCount() const - { - return m_count; - } - - LogSegment* GetSegment(uint32_t index) const - { - if (index > m_count || m_count == 0) { - return nullptr; - } - return m_segments[index]; - } - -private: - static constexpr uint32_t DEFAULT_SEGMENT_NUM = 1024; - - uint64_t m_transactionId; - - LogSegment** m_segments; - - uint32_t m_count; - - size_t m_size; - - uint32_t m_maxSegments; -}; -} // namespace MOT - -#endif /* REDO_LOG_TRANSACTION_SEGMENTS_H */ diff --git a/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.cpp b/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.cpp index 3d6d187f2..5e5bb5e38 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.cpp +++ b/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.cpp @@ -29,31 +29,27 @@ namespace MOT { DECLARE_LOGGER(SurrogateState, Recovery); SurrogateState::SurrogateState() + : m_insertsArray(nullptr), m_empty(true), m_maxConnections(GetGlobalConfiguration().m_maxConnections) +{} + +bool SurrogateState::Init() { - uint32_t maxConnections = GetGlobalConfiguration().m_maxConnections; - m_empty = true; - m_maxConnections = 0; - m_insertsArray = new (std::nothrow) uint64_t[maxConnections]; - if (m_insertsArray != nullptr) { - m_maxConnections = maxConnections; - errno_t erc = - memset_s(m_insertsArray, m_maxConnections * sizeof(uint64_t), 0, m_maxConnections * sizeof(uint64_t)); - securec_check(erc, "\0", "\0"); + m_insertsArray = new (std::nothrow) uint64_t[m_maxConnections]; + if (m_insertsArray == nullptr) { + return false; } + errno_t erc = memset_s(m_insertsArray, m_maxConnections * sizeof(uint64_t), 0, m_maxConnections * sizeof(uint64_t)); + securec_check(erc, "\0", "\0"); + return true; } SurrogateState::~SurrogateState() { if (m_insertsArray != nullptr) { delete[] m_insertsArray; + m_insertsArray = nullptr; } -} - -void SurrogateState::ExtractInfoFromKey(uint64_t key, uint64_t& pid, uint64_t& insertions) -{ - pid = key >> SurrogateKeyGenerator::KEY_BITS; - insertions = key & 0x0000FFFFFFFFFFFFULL; - insertions++; + m_empty = true; } void SurrogateState::UpdateMaxInsertions(uint64_t insertions, uint32_t pid) @@ -84,12 +80,17 @@ bool SurrogateState::UpdateMaxKey(uint64_t key) void SurrogateState::Merge(std::list& arrays, SurrogateState& global) { - std::list::iterator i; - for (i = arrays.begin(); i != arrays.end(); ++i) { - for (uint32_t j = 0; j < global.GetMaxConnections(); ++j) { - global.UpdateMaxInsertions((*i)[j], j); + for (auto i = arrays.begin(); i != arrays.end();) { + uint64_t* ptr = (*i); + if (ptr) { + for (uint32_t j = 0; j < global.GetMaxConnections(); ++j) { + global.UpdateMaxInsertions(ptr[j], j); + } + i = arrays.erase(i); + free(ptr); + } else { + ++i; } - delete[](*i); } } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.h b/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.h index aad1b3dae..51c553d3f 100644 --- a/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.h +++ b/src/gausskernel/storage/mot/core/system/recovery/surrogate_state.h @@ -39,6 +39,14 @@ class SurrogateState { public: SurrogateState(); + ~SurrogateState(); + + /** + * @brief Initializes the surrogate state. + * @return Boolean value denoting success or failure. + */ + bool Init(); + /** * @brief Extracts the surrogate counter from the key and updates * the array according to the connection id @@ -61,7 +69,12 @@ public: * @param pid The returned thread id. * @param insertions The returned number of insertions. */ - inline void ExtractInfoFromKey(uint64_t key, uint64_t& pid, uint64_t& insertions); + inline void ExtractInfoFromKey(uint64_t key, uint64_t& pid, uint64_t& insertions) const + { + pid = key >> SurrogateKeyGenerator::KEY_BITS; + insertions = key & 0x0000FFFFFFFFFFFFULL; + insertions++; + } /** * @brief merges some max insertions arrays into a one single state @@ -85,13 +98,6 @@ public: return m_maxConnections; } - bool IsValid() const - { - return (m_insertsArray != nullptr); - } - - ~SurrogateState(); - private: uint64_t* m_insertsArray; diff --git a/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.cpp b/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.cpp index be42cf1bd..149f3db01 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.cpp +++ b/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.cpp @@ -63,18 +63,18 @@ DbSessionStatisticsProvider::DbSessionStatisticsProvider() DbSessionStatisticsProvider::~DbSessionStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void DbSessionStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool DbSessionStatisticsProvider::CreateInstance() @@ -123,9 +123,9 @@ void DbSessionStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableDbSessionStatistics) { m_enable = GetGlobalConfiguration().m_enableDbSessionStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } diff --git a/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.h b/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.h index e088f250a..c13facde2 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.h +++ b/src/gausskernel/storage/mot/core/system/statistics/db_session_statistics.h @@ -29,7 +29,7 @@ #include "iconfig_change_listener.h" #include "numeric_statistic_variable.h" #include "statistics_provider.h" -#include "stats/frequency_statistic_variable.h" +#include "frequency_statistic_variable.h" #include "typed_statistics_generator.h" namespace MOT { @@ -239,14 +239,14 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; private: /** @brief Constructor. */ DbSessionStatisticsProvider(); /** @brief Destructor. */ - virtual ~DbSessionStatisticsProvider(); + ~DbSessionStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); diff --git a/src/gausskernel/storage/mot/core/system/statistics/network_statistics.cpp b/src/gausskernel/storage/mot/core/system/statistics/network_statistics.cpp index afd67442a..d2fa138e5 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/network_statistics.cpp +++ b/src/gausskernel/storage/mot/core/system/statistics/network_statistics.cpp @@ -71,18 +71,18 @@ NetworkStatisticsProvider::NetworkStatisticsProvider() NetworkStatisticsProvider::~NetworkStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void NetworkStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool NetworkStatisticsProvider::CreateInstance() @@ -131,9 +131,9 @@ void NetworkStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableNetworkStatistics) { m_enable = GetGlobalConfiguration().m_enableNetworkStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } diff --git a/src/gausskernel/storage/mot/core/system/statistics/network_statistics.h b/src/gausskernel/storage/mot/core/system/statistics/network_statistics.h index 8a4ad2d80..36c6af28d 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/network_statistics.h +++ b/src/gausskernel/storage/mot/core/system/statistics/network_statistics.h @@ -199,14 +199,14 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; private: /** @brief Constructor. */ NetworkStatisticsProvider(); /** @brief Destructor. */ - virtual ~NetworkStatisticsProvider(); + ~NetworkStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); diff --git a/src/gausskernel/storage/mot/core/system/statistics/process_statistics.cpp b/src/gausskernel/storage/mot/core/system/statistics/process_statistics.cpp index 2d33ec2ae..0d06a99c7 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/process_statistics.cpp +++ b/src/gausskernel/storage/mot/core/system/statistics/process_statistics.cpp @@ -50,9 +50,9 @@ ProcessStatisticsProvider::ProcessStatisticsProvider() { // register if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); // take initial snapshot SnapshotCpuStats(m_lastCpu, m_lastSysCpu, m_lastUserCpu); @@ -80,18 +80,18 @@ ProcessStatisticsProvider::ProcessStatisticsProvider() ProcessStatisticsProvider::~ProcessStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void ProcessStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool ProcessStatisticsProvider::CreateInstance() @@ -140,9 +140,9 @@ void ProcessStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableProcessStatistics) { m_enable = GetGlobalConfiguration().m_enableProcessStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } @@ -192,11 +192,11 @@ static void GetMemValues(unsigned long long& vmem, unsigned long long& pmem) break; } } - fclose(file); + (void)fclose(file); } } -void ProcessStatisticsProvider::SnapshotCpuStats(clock_t& cpu, clock_t& sysCpu, clock_t& userCpu) +void ProcessStatisticsProvider::SnapshotCpuStats(clock_t& cpu, clock_t& sysCpu, clock_t& userCpu) const { struct tms timeSample; cpu = times(&timeSample); @@ -209,7 +209,7 @@ void ProcessStatisticsProvider::SnapshotCpuStats(clock_t& cpu, clock_t& sysCpu, } bool ProcessStatisticsProvider::SnapMemoryIOStats( - uint64_t& minorFaults, uint64_t& majorFaults, uint64_t& inputOps, uint64_t& outputOps, uint64_t& maxRss) + uint64_t& minorFaults, uint64_t& majorFaults, uint64_t& inputOps, uint64_t& outputOps, uint64_t& maxRss) const { bool result = false; struct rusage usage; @@ -269,7 +269,7 @@ void ProcessStatisticsProvider::PrintMemoryInfo() text, lib); } - fclose(f); + (void)fclose(f); } // third way: from /proc/self/status @@ -296,8 +296,8 @@ void ProcessStatisticsProvider::PrintTotalCpuUsage() percentSys = -1.0; percentUser = -1.0; } else { - percentSys = ((double)(sysCpu - m_lastSysCpu)) / (cpu - m_lastCpu) / m_processorCount * 100.0f; - percentUser = ((double)(userCpu - m_lastUserCpu)) / (cpu - m_lastCpu) / m_processorCount * 100.0f; + percentSys = ((((double)(sysCpu - m_lastSysCpu)) / (cpu - m_lastCpu)) / m_processorCount) * 100.0f; + percentUser = ((((double)(userCpu - m_lastUserCpu)) / (cpu - m_lastCpu)) / m_processorCount) * 100.0f; percent = percentSys + percentUser; } diff --git a/src/gausskernel/storage/mot/core/system/statistics/process_statistics.h b/src/gausskernel/storage/mot/core/system/statistics/process_statistics.h index bc3f74b88..55c9fa9a8 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/process_statistics.h +++ b/src/gausskernel/storage/mot/core/system/statistics/process_statistics.h @@ -60,20 +60,20 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; protected: /** * @brief Override default behavior, and print current process status summary. */ - virtual void PrintStatisticsEx(); + void PrintStatisticsEx() override; private: /** @brief Constructor. */ ProcessStatisticsProvider(); /** @brief Destructor. */ - virtual ~ProcessStatisticsProvider(); + ~ProcessStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); @@ -114,7 +114,7 @@ private: * @param sysCpu Total CPU usage count in kernel-space. * @param userCpu Total CPU usage count in user-space. */ - void SnapshotCpuStats(clock_t& cpu, clock_t& sysCpu, clock_t& userCpu); + void SnapshotCpuStats(clock_t& cpu, clock_t& sysCpu, clock_t& userCpu) const; /** * @brief Take a snapshot of memory and I/O counters for this process. @@ -126,7 +126,7 @@ private: * @return True if operations succeeded and output data is valid, otherwise false. */ bool SnapMemoryIOStats( - uint64_t& minorFaults, uint64_t& majorFfaults, uint64_t& inputOps, uint64_t& outputOps, uint64_t& maxRss); + uint64_t& minorFaults, uint64_t& majorFfaults, uint64_t& inputOps, uint64_t& outputOps, uint64_t& maxRss) const; /** @brief Prints memory usage statistics. */ void PrintMemoryInfo(); diff --git a/src/gausskernel/storage/mot/core/system/statistics/system_statistics.cpp b/src/gausskernel/storage/mot/core/system/statistics/system_statistics.cpp index 36b23a195..e528c8e04 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/system_statistics.cpp +++ b/src/gausskernel/storage/mot/core/system/statistics/system_statistics.cpp @@ -50,16 +50,16 @@ SystemStatisticsProvider::SystemStatisticsProvider() void SystemStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } SystemStatisticsProvider::~SystemStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } @@ -79,8 +79,7 @@ bool SystemStatisticsProvider::CreateInstance() delete m_provider; m_provider = nullptr; } else { - result = m_provider->SnapshotInitialMetrics(); - if (!result) { + if (!m_provider->SnapshotInitialMetrics()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Statistics", "Failed to take initial metrics snapshot in System Statistics Provider, aborting"); @@ -88,6 +87,7 @@ bool SystemStatisticsProvider::CreateInstance() m_provider = nullptr; } else { m_provider->RegisterProvider(); + result = true; } } } @@ -120,9 +120,9 @@ void SystemStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableSystemStatistics) { m_enable = GetGlobalConfiguration().m_enableSystemStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } @@ -134,7 +134,7 @@ void SystemStatisticsProvider::PrintStatisticsEx() } bool SystemStatisticsProvider::SnapshotCpuStats( - uint64_t& totalUser, uint64_t& totalUserLow, uint64_t& totalSys, uint64_t& totalIdle) + uint64_t& totalUser, uint64_t& totalUserLow, uint64_t& totalSys, uint64_t& totalIdle) const { bool result = false; FILE* file = fopen("/proc/stat", "r"); @@ -152,7 +152,7 @@ bool SystemStatisticsProvider::SnapshotCpuStats( if (!result) { MOT_LOG_DEBUG("Failed to get system stats from /proc/stat: cpu line not found"); } - fclose(file); + (void)fclose(file); } else { MOT_LOG_DEBUG("Failed to get system stats from /proc/stat: cannot open file for reading"); } @@ -165,10 +165,13 @@ bool SystemStatisticsProvider::SnapshotCpuStats( return result; } -void SystemStatisticsProvider::PrintMemoryInfo() +void SystemStatisticsProvider::PrintMemoryInfo() const { struct sysinfo memInfo; - sysinfo(&memInfo); + if (sysinfo(&memInfo) != 0) { + MOT_LOG_TRACE("Failed to retrieve sysinfo"); + return; + } // total virtual memory in the system uint64_t totalVirtualMem = memInfo.totalram; @@ -222,8 +225,8 @@ void SystemStatisticsProvider::PrintTotalCpuUsage() diffIdle = (totalIdle - m_lastTotalIdle); total = diffUser + diffUserLow + diffSys + diffIdle; if (total > 0) { - percentUser = ((double)(diffUser + diffUserLow)) / total * 100.0f; - percentSys = ((double)diffSys) / total * 100.0f; + percentUser = (((double)(diffUser + diffUserLow)) / total) * 100.0f; + percentSys = (((double)diffSys) / total) * 100.0f; percent = percentUser + percentSys; } } diff --git a/src/gausskernel/storage/mot/core/system/statistics/system_statistics.h b/src/gausskernel/storage/mot/core/system/statistics/system_statistics.h index 417559f8d..8900d66ef 100644 --- a/src/gausskernel/storage/mot/core/system/statistics/system_statistics.h +++ b/src/gausskernel/storage/mot/core/system/statistics/system_statistics.h @@ -60,20 +60,20 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; protected: /** * @brief Override default behavior, and print current system status summary. */ - virtual void PrintStatisticsEx(); + void PrintStatisticsEx() override; private: /** @brief Constructor. */ SystemStatisticsProvider(); /** @brief Destructor. */ - virtual ~SystemStatisticsProvider(); + ~SystemStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); @@ -104,10 +104,10 @@ private: * @param totalIdle Total time spent in the idle task. * @return True if operations succeeded and output data is valid, otherwise false. */ - bool SnapshotCpuStats(uint64_t& totalUser, uint64_t& totalUserLow, uint64_t& totalSys, uint64_t& totalIdle); + bool SnapshotCpuStats(uint64_t& totalUser, uint64_t& totalUserLow, uint64_t& totalSys, uint64_t& totalIdle) const; /** @brief Prints memory usage statistics. */ - void PrintMemoryInfo(); + void PrintMemoryInfo() const; /** @brief Prints CPU usage statistics. */ void PrintTotalCpuUsage(); diff --git a/src/gausskernel/storage/mot/core/system/transaction/access.cpp b/src/gausskernel/storage/mot/core/system/transaction/access.cpp new file mode 100644 index 000000000..6bf7fbf0a --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction/access.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * access.cpp + * Holds data for single row access. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction/access.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "row.h" +#include "sentinel.h" +#include "access_params.h" +#include "access.h" +#include "txn_access.h" + +namespace MOT { +IMPLEMENT_CLASS_LOGGER(Access, Transaction); + +void Access::Print() const +{ + MOT_LOG_INFO("---------------------------------------------------------------"); + MOT_LOG_INFO("Access Type = %s", TxnAccess::enTxnStates[m_type]); + MOT_LOG_INFO("Table = %s", GetSentinel()->GetIndex()->GetTable()->GetTableName().c_str()); + MOT_LOG_INFO("Index = %s", GetSentinel()->GetIndex()->GetName().c_str()); + MOT_LOG_INFO("Index Order = %s", enIndexOrder[static_cast(GetSentinel()->GetIndexOrder())]); + if (m_type == INS) { + MOT_LOG_INFO("Upgrade Insert = %s", m_params.IsUpgradeInsert() ? "true" : "false"); + MOT_LOG_INFO("Insert on delete = %s", m_params.IsInsertOnDeletedRow() ? "true" : "false"); + } else { + if (m_type != RD) { + MOT_LOG_INFO("Global Row CSN = %lu", GetGlobalVersion()->GetCommitSequenceNumber()); + } + } + MOT_LOG_INFO("Buffer ID = %d", GetBufferId()); +} + +void Access::WriteGlobalChanges(uint64_t csn, uint64_t transaction_id) +{ + if (m_type == RD) { + return; + } + + Row* newVersion = nullptr; + MOT_ASSERT(m_origSentinel->IsLocked() == true); + switch (m_type) { + case WR: + MOT_ASSERT(m_params.IsPrimarySentinel() == true); + MOT_ASSERT(m_globalRow->GetCommitSequenceNumber() == m_origSentinel->GetData()->GetCommitSequenceNumber()); + newVersion = m_localRow; + newVersion->SetTable(m_localRow->GetOrigTable()); + newVersion->SetCommitSequenceNumber(csn); + MOT_ASSERT(m_globalRow == m_origSentinel->GetData()); + newVersion->SetNextVersion(GetGlobalVersion()); + COMPILER_BARRIER; + m_origSentinel->SetNextPtr(newVersion); + COMPILER_BARRIER; + m_origSentinel->SetWriteBit(); + break; + case DEL: + if (m_params.IsPrimarySentinel() == true) { + newVersion = m_localRow; + MOT_ASSERT(newVersion->IsRowDeleted() == true); + MOT_ASSERT( + m_globalRow->GetCommitSequenceNumber() == m_origSentinel->GetData()->GetCommitSequenceNumber()); + newVersion->SetTable(m_localRow->GetOrigTable()); + newVersion->SetCommitSequenceNumber(csn); + MOT_ASSERT(m_globalRow == m_origSentinel->GetData()); + newVersion->SetNextVersion(GetGlobalVersion()); + COMPILER_BARRIER; + m_origSentinel->SetNextPtr(newVersion); + COMPILER_BARRIER; + m_origSentinel->SetWriteBit(); + m_localRow->GetTable()->UpdateRowCount(-1); + } else { + m_origSentinel->SetEndCSN(csn); + } + break; + case INS: + if (m_params.IsPrimarySentinel()) { + GetLocalInsertRow()->SetCommitSequenceNumber(csn); + GetLocalInsertRow()->SetTable(GetLocalInsertRow()->GetOrigTable()); + if (m_params.IsUpgradeInsert() == true) { + m_origSentinel->SetStartCSN(csn); + MOT_ASSERT(m_globalRow == m_origSentinel->GetData()); + if (m_params.IsInsertOnDeletedRow()) { + GetLocalInsertRow()->SetNextVersion(GetGlobalVersion()); + } else { + // Connect the new insert to a tombstone + MOT_ASSERT(m_localRow->IsRowDeleted() == true); + // Set csn to the tombstome + m_localRow->SetCommitSequenceNumber(csn); + m_localRow->SetTable(m_localRow->GetOrigTable()); + GetLocalInsertRow()->SetNextVersion(m_localRow); + m_localRow->SetNextVersion(GetGlobalVersion()); + // NewVersion--->Tombstone--->OldVersion + } + } + } + break; + default: + break; + } +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/access.h b/src/gausskernel/storage/mot/core/system/transaction/access.h index 7ec06a27f..7451fd0c1 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/access.h +++ b/src/gausskernel/storage/mot/core/system/transaction/access.h @@ -22,37 +22,68 @@ * ------------------------------------------------------------------------- */ -#pragma once - #ifndef MOT_ACCESS_H #define MOT_ACCESS_H -#include +#include namespace MOT { /** * @class Access * @brief Holds data for single row access. */ -class Access { +class Access { public: - explicit Access(uint32_t id) : m_localRowSize(0), m_bufferId(id) - {} + explicit Access(uint32_t id) : m_csn(0), m_snapshot(0), m_stmtCount(0), m_redoStmt(0), m_ops(0), m_bufferId(id) + { + m_modifiedColumns.Reset(); + } ~Access() - {} + { + m_localInsertRow = nullptr; + m_globalRow = nullptr; + m_localRow = nullptr; + m_origSentinel = nullptr; + m_secondaryUniqueNode = nullptr; + m_secondaryDelKey = nullptr; + } + + void Print() const; + + void WriteGlobalChanges(uint64_t csn, uint64_t transaction_id); void ResetUsedParameters() { m_localInsertRow = nullptr; - m_auxRow = nullptr; + m_globalRow = nullptr; + m_localRow = nullptr; m_origSentinel = nullptr; + m_secondaryUniqueNode = nullptr; + m_secondaryDelKey = nullptr; m_stmtCount = 0; + m_redoStmt = 0; + m_ops = 0; + m_csn = 0; + m_snapshot = 0; m_params.AssignParams(0); } - /** @var Access type. */ + inline Row* GetLocalVersion() + { + if (m_type != INS) { + return m_localRow; + } else { + return m_localInsertRow; + } + } + + inline Row* GetGlobalVersion() const + { + return m_globalRow; + } + inline Sentinel* GetSentinel() const { return m_origSentinel; @@ -61,7 +92,7 @@ public: /** * @brief Get row from header * for inserts the local header if mapped is the local draft - * @return Pointer to the currrent row + * @return Pointer to the current row */ inline Row* GetRowFromHeader() const { @@ -71,20 +102,10 @@ public: return m_origSentinel->GetData(); } - /** @var The original row */ - Row* m_localInsertRow = nullptr; - - /** @var The modified draft row. */ - Row* m_localRow = nullptr; - - /** @var The auxiliary row */ - Row* m_auxRow = nullptr; - - /** @var The original row header */ - Sentinel* m_origSentinel = nullptr; - - /** @var The bitmap set represents the updated columns. */ - BitmapSet m_modifiedColumns; + inline Row* GetLocalInsertRow() const + { + return m_localInsertRow; + } /** * @brief Gets a consistent local copy of the row which is used by the OCC transaction. @@ -96,29 +117,19 @@ public: */ inline Row* GetTxnRow() const { + if (m_type == RD or m_type == RD_FOR_UPDATE) { + return m_globalRow; + } if (m_type != INS) { return m_localRow; } else { - if (m_params.IsUpgradeInsert()) { - return m_auxRow; - } else - return m_localInsertRow; + return m_localInsertRow; } } - /** - * @brief When validating INS operation we want to verify no - * other commiter commited before us. - * for regular insert we start by pointing at a null pointer - * @return row pointer - */ - inline Row* GetRecordedSentinelRow() const + inline AccessType GetType() const { - if (m_params.IsUpgradeInsert() == false) { - return nullptr; - } else { - return GetSentinel()->GetData(); - } + return m_type; } inline uint32_t GetBufferId() const @@ -131,8 +142,60 @@ public: m_bufferId = id; } - /** @var OCC transaction identifier. */ - TransactionId m_tid = 0; + void IncreaseOps() + { + m_ops++; + } + + uint16_t GetOpsCount() const + { + return m_ops; + } + + uint32_t GetStmtCount() const + { + return m_stmtCount; + } + + uint32_t GetRedoStmt() const + { + return m_redoStmt; + } + + /** @var The global visible sentinel */ + Sentinel* m_origSentinel = nullptr; + + /** @var The global visible version */ + Row* m_globalRow = nullptr; + + /** @var The inserted row for the INS operation */ + Row* m_localInsertRow = nullptr; + + /** @var The modified draft row. */ + Row* m_localRow = nullptr; + + /** @var Key used for delete from secondary index in case of update on indexed column */ + Key* m_secondaryDelKey = nullptr; + + /** @var the snapshot of the current operation */ + uint64_t m_csn = 0; + + uint64_t m_snapshot = 0; + + /** @var The bitmap set represents the updated columns. */ + BitmapSet m_modifiedColumns; + + /** @var The secondary-sentinel unique cached node */ + PrimarySentinelNode* m_secondaryUniqueNode = nullptr; + + /** @var Transaction statement counter */ + uint32_t m_stmtCount = 0; + + /** @var redo statement index */ + uint32_t m_redoStmt = 0; + + /** @var number of operation performed in a single query */ + uint16_t m_ops = 0; /** @var Local access parameters */ AccessParams m_params; @@ -140,11 +203,6 @@ public: /** @var Row state */ AccessType m_type = AccessType::INV; - /** @var Transaction statement counter */ - uint32_t m_stmtCount = 0; - - uint32_t m_localRowSize; - private: /** * @brief Swap current id with other Access @@ -162,6 +220,8 @@ private: uint32_t m_bufferId; friend class TxnAccess; + + DECLARE_CLASS_LOGGER(); }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/access_params.h b/src/gausskernel/storage/mot/core/system/transaction/access_params.h index 1eb963630..d54ae8251 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/access_params.h +++ b/src/gausskernel/storage/mot/core/system/transaction/access_params.h @@ -27,16 +27,19 @@ #ifndef ACCESS_PARAMS_H #define ACCESS_PARAMS_H -#include +#include #include +#include "logger.h" namespace MOT { enum AccessFlags : uint8_t { - primary_sentinel_bit = (1U << 0), + primary_sentinel_bit = (1U), unique_index_bit = (1U << 1), row_commited_bit = (1U << 2), upgrade_insert_bit = (1U << 3), dummy_deleted_bit = (1U << 4), + index_update_bit = (1U << 5), + update_deleted_bit = (1U << 6), }; /** @@ -54,24 +57,35 @@ public: static_assert(sizeof(T) == sizeof(AccessFlags), "Sizes are different"); }; + __attribute__((noinline)) void Print() const + { + MOT_LOG_INFO("PrimarySentinel: %s", IsPrimarySentinel() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("UniqueIndex: %s", IsUniqueIndex() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("RowCommited: %s", IsRowCommited() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("UpgradeInsert: %s", IsUpgradeInsert() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("InsertOnDeleted: %s", IsInsertOnDeletedRow() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("IndexUpdate: %s", IsIndexUpdate() ? "TRUE" : "FALSE"); + MOT_LOG_INFO("UpdateDeleted: %s", IsUpdateDeleted() ? "TRUE" : "FALSE"); + } + bool IsPrimarySentinel() const { - return m_value & primary_sentinel_bit; + return static_cast(m_value & primary_sentinel_bit); } bool IsUniqueIndex() const { - return m_value & unique_index_bit; + return static_cast(m_value & unique_index_bit); } bool IsRowCommited() const { - return m_value & row_commited_bit; + return static_cast(m_value & row_commited_bit); } bool IsUpgradeInsert() const { - return m_value & upgrade_insert_bit; + return static_cast(m_value & upgrade_insert_bit); } bool IsPrimaryUpgrade() const @@ -79,9 +93,24 @@ public: return (IsPrimarySentinel() and IsUpgradeInsert()); } - bool IsDummyDeletedRow() const + bool IsSecondaryUniqueSentinel() const { - return m_value & dummy_deleted_bit; + return !IsPrimarySentinel() and IsUniqueIndex(); + } + + bool IsInsertOnDeletedRow() const + { + return static_cast(m_value & dummy_deleted_bit); + } + + bool IsIndexUpdate() const + { + return static_cast(m_value & index_update_bit); + } + + bool IsUpdateDeleted() const + { + return static_cast(m_value & update_deleted_bit); } void SetPrimarySentinel() @@ -117,18 +146,39 @@ public: void UnsetUpgradeInsert() { m_value &= ~upgrade_insert_bit; + UnsetInsertOnDeletedRow(); } - void SetDummyDeletedRow() + void SetInsertOnDeletedRow() { m_value |= dummy_deleted_bit; } - void UnsetDeletedCommitedRow() + void SetIndexUpdate() + { + m_value |= index_update_bit; + } + + void UnsetIndexUpdate() + { + m_value &= ~index_update_bit; + } + + void UnsetInsertOnDeletedRow() { m_value &= ~dummy_deleted_bit; } + void SetUpdateDeleted() + { + m_value |= update_deleted_bit; + } + + void UnsetUpdateDeleted() + { + m_value &= ~update_deleted_bit; + } + void AssignParams(T x) { m_value = x; @@ -140,6 +190,8 @@ private: AccessParams(T v) : m_value(v) {} + + DECLARE_CLASS_LOGGER(); }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.cpp b/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.cpp new file mode 100644 index 000000000..ba991a71d --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * sub_txn_mgr.cpp + * Implements SubTxnMgr which is used to manage sub-transactions + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "txn.h" +#include "txn_access.h" + +namespace MOT { +void SubTxnMgr::Start(uint64_t subTransactionId) +{ + TxnAccess* access_manager = m_manager->m_accessMgr; + if (m_subTxnStack.empty()) { + m_isSubTxnStarted = true; + } + SubTxnNode s; + s.m_snapshotAccessId = access_manager->Size(); + s.m_subTransactionId = subTransactionId; + s.m_subOper = SubTxnOperType::SUB_TXN_READ; + s.m_isSubTxnStartedEmpty = m_manager->IsReadOnlyTxn(); + m_subTxnStack.push(s); +} + +void SubTxnMgr::SetSubTxnOper(SubTxnOperType subOper) +{ + if (m_isSubTxnStarted) { + SubTxnNode& s = m_subTxnStack.top(); + s.SetSubOper(subOper); + } +} + +void SubTxnMgr::SetHardTxnAbort() +{ + m_manager->SetTxnAborted(); +} + +void SubTxnMgr::SetHasCommitedTxnDDL() +{ + m_manager->SetHasCommitedSubTxnDDL(); +} + +bool SubTxnMgr::IsHardTxnAbort() const +{ + return m_manager->IsTxnAborted(); +} + +RC SubTxnMgr::Rollback(uint64_t subTransactionId) +{ + RC rc = RC_OK; + if (m_isSubTxnStarted) { + SubTxnNode& s = m_subTxnStack.top(); + if (s.m_subTransactionId < subTransactionId) { + return rc; + } + MOT_ASSERT(s.m_subTransactionId == subTransactionId); + if (IsHardTxnAbort() == false) { + if (s.GetSubOper() == SubTxnOperType::SUB_TXN_READ) { + // Cleanup of SELECT/SELECT-FOR-UPDATE + m_manager->m_accessMgr->RollbackSubTxn(s.m_snapshotAccessId); + } else { + // Scenario not recoverable + if (s.IsSubTxnStartedEmpty() == false) { + SetHardTxnAbort(); + } + rc = RC_ABORT; + } + } else { + // Txn is already rollbacked. + rc = RC_OK; + } + m_subTxnStack.pop(); + if (m_subTxnStack.empty()) { + m_isSubTxnStarted = false; + } + } else { + if (m_manager->IsReadOnlyTxn() == false) { + rc = RC_ABORT; + } + } + return rc; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.h b/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.h new file mode 100644 index 000000000..374827750 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * sub_txn_mgr.h + * Manages the current sub-transaction. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction/sub_txn_mgr.h + * + * ------------------------------------------------------------------------- + */ + +#pragma once + +#ifndef SUB_TXN_MGR_H +#define SUB_TXN_MGR_H + +#include +#include +#include "debug_utils.h" + +namespace MOT { +/** + * @class SubTxnMgr + * @brief Manages the current sub-transaction. + * Allows rollback of RD-ONLY sub-transactions. In case of abort of a sub-transaction that contains DDL/DML, + * the root transaction and all sub transactions are aborted. + */ +class SubTxnMgr { +public: + struct PACKED SubTxnNode { + /** @var Sub-transaction ID. */ + uint64_t m_subTransactionId = 0; + + /** @var Access snapshot ID. */ + uint32_t m_snapshotAccessId = 0; + + /** @var A flag indicating whether the sub-transaction is read-only. */ + SubTxnOperType m_subOper = SubTxnOperType::SUB_TXN_READ; + + bool m_isSubTxnStartedEmpty = true; + + void SetSubOper(SubTxnOperType subOper) + { + if (m_subOper < subOper) { + m_subOper = subOper; + } + } + + SubTxnOperType GetSubOper() const + { + return m_subOper; + } + + bool IsSubTxnStartedEmpty() const + { + return m_isSubTxnStartedEmpty; + } + }; + + /** Constructor. */ + SubTxnMgr() + {} + + /** Destructor. */ + ~SubTxnMgr() + { + m_manager = nullptr; + } + + /** @brief Init manager */ + inline bool Init(TxnManager* manager) + { + if (!manager) { + return false; + } + m_manager = manager; + m_isSubTxnStarted = false; + return true; + } + + /** @brief Start sub-transaction */ + void Start(uint64_t subTransactionId); + + /** @brief Commit sub-transaction */ + inline void Commit(uint64_t subTransactionId) + { + if (m_isSubTxnStarted) { + if (!m_subTxnStack.empty()) { + SubTxnNode& s = m_subTxnStack.top(); + MOT_ASSERT(s.m_subTransactionId == subTransactionId); + SubTxnOperType subOper = s.GetSubOper(); + m_subTxnStack.pop(); + if (subOper == SubTxnOperType::SUB_TXN_DDL) { + SetHasCommitedTxnDDL(); + } + if (m_subTxnStack.empty()) { + m_isSubTxnStarted = false; + } else if (subOper != SubTxnOperType::SUB_TXN_READ) { + SubTxnNode& s1 = m_subTxnStack.top(); + s1.SetSubOper(subOper); + } + } + } + } + + /** @brief Set RD-only flag */ + void SetSubTxnOper(SubTxnOperType subOper); + + /** @brief Rollback sub-transaction */ + RC Rollback(uint64_t subTransactionId); + + /** @brief Reset local arguments */ + inline void ResetSubTransactionMgr() + { + if (m_isSubTxnStarted == true) { + m_isSubTxnStarted = false; + m_subTxnStack = std::stack(); + MOT_ASSERT(m_subTxnStack.empty()); + } + } + + inline bool IsSubTxnStarted() const + { + return m_isSubTxnStarted; + } + + bool IsHardTxnAbort() const; + + void SetHardTxnAbort(); + + void SetHasCommitedTxnDDL(); + +private: + /** @var The parent transaction manager object. */ + TxnManager* m_manager = nullptr; + + /** @var A stack containing all the current sub-transaction info */ + std::stack m_subTxnStack; + + /** @var A flag indicating whether the sub-transaction started. */ + bool m_isSubTxnStarted = false; +}; +} // namespace MOT + +#endif // SUB_TXN_MGR_H diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn.cpp b/src/gausskernel/storage/mot/core/system/transaction/txn.cpp index 92253caca..cd9207f88 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction/txn.cpp @@ -22,11 +22,11 @@ * ------------------------------------------------------------------------- */ -#include +#include #include #include -#include "table.h" +#include "txn_table.h" #include "mot_engine.h" #include "redo_log_writer.h" #include "sentinel.h" @@ -36,25 +36,53 @@ #include "db_session_statistics.h" #include "utilities.h" #include "mm_api.h" +#include "txn_local_allocators.h" namespace MOT { DECLARE_LOGGER(TxnManager, System); +bool TxnManager::IsReadOnlyTxn() const +{ + return ((m_accessMgr->Size() == 0) and (m_txnDdlAccess->Size() == 0)); +} + +bool TxnManager::hasDDL() const +{ + return (m_txnDdlAccess->Size() > 0); +} + void TxnManager::RemoveTableFromStat(Table* t) { m_accessMgr->RemoveTableFromStat(t); } -void TxnManager::UpdateRow(Row* row, const int attr_id, double attr_value) +RC TxnManager::UpdateRow(Row* row, const int attr_id, double attr_value) { - row->SetValue(attr_id, attr_value); - UpdateLastRowState(AccessType::WR); + RC rc = UpdateLastRowState(AccessType::WR); + if (rc != RC_OK) { + return rc; + } + + Row* draftVersion = m_accessMgr->GetLastAccess()->GetLocalVersion(); + draftVersion->SetValue(attr_id, attr_value); + return rc; } -void TxnManager::UpdateRow(Row* row, const int attr_id, uint64_t attr_value) +RC TxnManager::UpdateRow(Row* row, const int attr_id, uint64_t attr_value) { - row->SetValue(attr_id, attr_value); - UpdateLastRowState(AccessType::WR); + RC rc = UpdateLastRowState(AccessType::WR); + if (rc != RC_OK) { + return rc; + } + + Row* draftVersion = m_accessMgr->GetLastAccess()->GetLocalVersion(); + draftVersion->SetValue(attr_id, attr_value); + return rc; +} + +Row* TxnManager::GetLastAccessedDraft() +{ + return m_accessMgr->GetLastAccessedDraft(); } InsItem* TxnManager::GetNextInsertItem(Index* index) @@ -64,22 +92,57 @@ InsItem* TxnManager::GetNextInsertItem(Index* index) Key* TxnManager::GetTxnKey(MOT::Index* index) { - int size = index->GetAlignedKeyLength() + sizeof(Key); - void* buf = MemSessionAlloc(size); - if (buf == nullptr) { - return nullptr; - } - return new (buf) Key(index->GetAlignedKeyLength()); + return m_dummyIndex->CreateNewKey(index); } -RC TxnManager::InsertRow(Row* row) +void TxnManager::DestroyTxnKey(Key* key) const { - GcSessionStart(); - RC result = m_accessMgr->GetInsertMgr()->ExecuteOptimisticInsert(row); - if (result == RC_OK) { + m_dummyIndex->DestroyKey(key); +} + +RC TxnManager::InsertRow(Row* row, Key* updateColumnKey) +{ + RC rc = RC_OK; + rc = SetSnapshot(); + SetTxnOper(SubTxnOperType::SUB_TXN_DML); + if (rc != RC_OK) { + return rc; + } + + rc = m_accessMgr->GetInsertMgr()->ExecuteOptimisticInsert(row, updateColumnKey); + if (rc == RC_OK) { + m_accessMgr->IncreaseTableStat(row->GetTable()); MOT::DbSessionStatisticsProvider::GetInstance().AddInsertRow(); } - return result; + return rc; +} + +RC TxnManager::InsertRecoveredRow(Row* row) +{ + RC rc = RC_OK; + rc = SetSnapshot(); + SetTxnOper(SubTxnOperType::SUB_TXN_DML); + if (rc != RC_OK) { + return rc; + } + + rc = m_accessMgr->GetInsertMgr()->ExecuteRecoveryOCCInsert(row); + if (rc == RC_OK) { + m_accessMgr->IncreaseTableStat(row->GetTable()); + MOT::DbSessionStatisticsProvider::GetInstance().AddInsertRow(); + } + return rc; +} + +bool TxnManager::IsRowExist(Sentinel* const& sentinel, RC& rc) +{ + rc = RC_OK; + Row* row = RowLookup(RD, sentinel, rc); + if (row != nullptr) { + return true; + } + + return false; } Row* TxnManager::RowLookup(const AccessType type, Sentinel* const& originalSentinel, RC& rc) @@ -91,34 +154,45 @@ Row* TxnManager::RowLookup(const AccessType type, Sentinel* const& originalSenti if (unlikely(originalSentinel == nullptr)) { return nullptr; } - // if txn not started, tag as started and take global epoch - GcSessionStart(); + // Set Snapshot for statement + rc = SetSnapshot(); + if (rc != RC_OK) { + return nullptr; + } + + Prefetch((const void*)(originalSentinel)); + // if txn not started, tag as started and take global epoch RC res = AccessLookup(type, originalSentinel, local_row); switch (res) { + case RC::RC_LOCAL_ROW_FOUND: + break; + case RC::RC_LOCAL_ROW_NOT_FOUND: + // For Read-Only Txn return the Commited row + if (type == AccessType::RD or type == AccessType::INS) { + local_row = m_accessMgr->GetVisibleRowVersion(originalSentinel, GetVisibleCSN()); + } else { + // Row is not in the cache,map it and return the local row + local_row = m_accessMgr->MapRowtoLocalTable(type, originalSentinel, rc); + } + break; + case RC::RC_LOCAL_ROW_NOT_VISIBLE: case RC::RC_LOCAL_ROW_DELETED: return nullptr; - case RC::RC_LOCAL_ROW_FOUND: - return local_row; - case RC::RC_LOCAL_ROW_NOT_FOUND: - if (likely(originalSentinel->IsCommited() == true)) { - // For Read-Only Txn return the Committed row - if (GetTxnIsoLevel() == READ_COMMITED and (type == RD or type == INS)) { - return m_accessMgr->GetReadCommitedRow(originalSentinel); - } else { - // Row is not in the cache,map it and return the local row - AccessType rd_type = (type != RD_FOR_UPDATE) ? RD : RD_FOR_UPDATE; - return m_accessMgr->MapRowtoLocalTable(rd_type, originalSentinel, rc); - } - } else - return nullptr; + case RC_PRIMARY_SENTINEL_NOT_MAPPED: + local_row = m_accessMgr->FetchRowFromPrimarySentinel(type, originalSentinel, rc); + break; case RC::RC_MEMORY_ALLOCATION_ERROR: rc = RC_MEMORY_ALLOCATION_ERROR; return nullptr; default: return nullptr; } + if (local_row) { + return m_accessMgr->GetRowZeroCopyIfAny(local_row); + } + return nullptr; } RC TxnManager::AccessLookup(const AccessType type, Sentinel* const& originalSentinel, Row*& localRow) @@ -129,6 +203,7 @@ RC TxnManager::AccessLookup(const AccessType type, Sentinel* const& originalSent RC TxnManager::DeleteLastRow() { RC rc; + SetTxnOper(SubTxnOperType::SUB_TXN_DML); Access* access = m_accessMgr->GetLastAccess(); if (access == nullptr) return RC_ERROR; @@ -136,7 +211,6 @@ RC TxnManager::DeleteLastRow() rc = m_accessMgr->UpdateRowState(AccessType::DEL, access); if (rc != RC_OK) return rc; - access->m_stmtCount = GetStmtCount(); return rc; } @@ -149,21 +223,36 @@ bool TxnManager::IsUpdatedInCurrStmt() { Access* access = m_accessMgr->GetLastAccess(); if (access == nullptr) { - return false; + return true; } - if (m_internalStmtCount == m_accessMgr->GetLastAccess()->m_stmtCount) { + if (access->GetOpsCount() > 0 and access->GetStmtCount() == GetStmtCount()) { return true; } return false; } -RC TxnManager::StartTransaction(uint64_t transactionId, int isolationLevel) +void TxnManager::StartTransaction(uint64_t transactionId, int isolationLevel) { m_transactionId = transactionId; - m_isolationLevel = isolationLevel; + m_isolationLevel = static_cast(isolationLevel); m_state = TxnState::TXN_START; - GcSessionStart(); - return RC_OK; +} + +void TxnManager::StartSubTransaction(uint64_t subTransactionId, int isolationLevel) +{ + m_subTxnManager.Start(subTransactionId); +} + +void TxnManager::CommitSubTransaction(uint64_t subTransactionId) +{ + m_subTxnManager.Commit(subTransactionId); +} + +RC TxnManager::RollbackSubTransaction(uint64_t subTransactionId) +{ + RC rc = RC_OK; + rc = m_subTxnManager.Rollback(subTransactionId); + return rc; } void TxnManager::LiteRollback() @@ -190,6 +279,7 @@ void TxnManager::RollbackInternal(bool isPrepared) if (isPrepared) { if (GetGlobalConfiguration().m_enableCheckpoint) { GetCheckpointManager()->FreePreAllocStableRows(this); + GetCheckpointManager()->EndCommit(this); } m_occManager.ReleaseHeaders(this); @@ -199,7 +289,7 @@ void TxnManager::RollbackInternal(bool isPrepared) } // We have to undo changes to secondary indexes and ddls - m_occManager.RollbackInserts(this); + UndoLocalDMLChanges(); RollbackDDLs(); Cleanup(); if (isPrepared) { @@ -216,10 +306,27 @@ void TxnManager::CleanTxn() RC TxnManager::Prepare() { + RC rc = RC_OK; + bool validateOccIsDone = false; + if (GetGlobalConfiguration().m_enableCheckpoint) { + GetCheckpointManager()->BeginCommit(this); + } + if (m_txnDdlAccess->Size() != 0) { + rc = m_txnDdlAccess->ValidateDDLChanges(this); + } // Run only first validation phase - RC rc = m_occManager.ValidateOcc(this); if (rc == RC_OK) { - m_redoLog.Prepare(); + rc = m_occManager.ValidateOcc(this); + } + if (rc == RC_OK) { + validateOccIsDone = true; + rc = m_redoLog.Prepare(); + } + if (rc != RC_OK && GetGlobalConfiguration().m_enableCheckpoint) { + if (validateOccIsDone) { + GetCheckpointManager()->FreePreAllocStableRows(this); + } + GetCheckpointManager()->EndCommit(this); } return rc; } @@ -230,17 +337,18 @@ void TxnManager::LitePrepare() return; } - m_redoLog.Prepare(); + (void)m_redoLog.Prepare(); } void TxnManager::CommitInternal() { - if (m_csn == CSNManager::INVALID_CSN) { + if (m_csn == INVALID_CSN) { SetCommitSequenceNumber(GetCSNManager().GetNextCSN()); } // first write to redo log, then write changes - m_redoLog.Commit(); + (void)m_redoLog.Commit(); + ApplyDDLChanges(); m_occManager.WriteChanges(this); if (GetGlobalConfiguration().m_enableCheckpoint) { @@ -254,7 +362,28 @@ void TxnManager::CommitInternal() RC TxnManager::ValidateCommit() { - return m_occManager.ValidateOcc(this); + RC res = RC_OK; + if (IsTxnAborted()) { + return MOT::RC_ABORT; + } + if (!IsReadOnlyTxn()) { + if (!IsRecoveryTxn() and MOTEngine::GetInstance()->IsRecovering()) { + return MOT::RC_ABORT; + } + } + if (GetGlobalConfiguration().m_enableCheckpoint) { + GetCheckpointManager()->BeginCommit(this); + } + if (m_txnDdlAccess->Size() != 0) { + res = m_txnDdlAccess->ValidateDDLChanges(this); + } + if (res == RC_OK) { + res = m_occManager.ValidateOcc(this); + } + if (res != RC_OK && GetGlobalConfiguration().m_enableCheckpoint) { + GetCheckpointManager()->EndCommit(this); + } + return res; } void TxnManager::RecordCommit() @@ -265,6 +394,10 @@ void TxnManager::RecordCommit() RC TxnManager::Commit() { + if (IsReadOnlyTxn()) { + return RC_OK; + } + // Validate concurrency control RC rc = ValidateCommit(); if (rc == RC_OK) { @@ -277,8 +410,8 @@ void TxnManager::LiteCommit() { if (m_txnDdlAccess->Size() > 0) { // write to redo log - m_redoLog.Commit(); - CleanDDLChanges(); + (void)m_redoLog.Commit(); + ApplyDDLChanges(); Cleanup(); } MOT::DbSessionStatisticsProvider::GetInstance().AddCommitTxn(); @@ -286,12 +419,13 @@ void TxnManager::LiteCommit() void TxnManager::CommitPrepared() { - if (m_csn == CSNManager::INVALID_CSN) { + if (m_csn == INVALID_CSN) { SetCommitSequenceNumber(GetCSNManager().GetNextCSN()); } // first write to redo log, then write changes m_redoLog.CommitPrepared(); + ApplyDDLChanges(); m_occManager.WriteChanges(this); if (GetGlobalConfiguration().m_enableCheckpoint) { @@ -309,7 +443,7 @@ void TxnManager::LiteCommitPrepared() if (m_txnDdlAccess->Size() > 0) { // first write to redo log, then write changes m_redoLog.CommitPrepared(); - CleanDDLChanges(); + ApplyDDLChanges(); Cleanup(); } MOT::DbSessionStatisticsProvider::GetInstance().AddCommitPreparedTxn(); @@ -317,10 +451,11 @@ void TxnManager::LiteCommitPrepared() void TxnManager::EndTransaction() { - if (GetGlobalConfiguration().m_enableRedoLog) { - m_occManager.ReleaseLocks(this); + if (!IsReadOnlyTxn()) { + if (GetGlobalConfiguration().m_enableRedoLog) { + m_occManager.ReleaseLocks(this); + } } - CleanDDLChanges(); Cleanup(); } @@ -328,44 +463,80 @@ void TxnManager::RedoWriteAction(bool isCommit) { m_redoLog.SetForceWrite(); if (isCommit) - m_redoLog.Commit(); + (void)m_redoLog.Commit(); else m_redoLog.Rollback(); } -void TxnManager::Cleanup() +inline void TxnManager::Cleanup() { - if (m_isLightSession == false) { + if (m_occManager.IsTransactionCommited()) { + m_accessMgr->GcMaintenance(); + } + if (!m_isLightSession) { m_accessMgr->ClearSet(); } + m_isTxnAborted = false; + m_hasCommitedSubTxnDDL = false; + m_isCrossEngineTxn = false; m_txnDdlAccess->Reset(); + if (!m_checkpointCommitEnded) { + GetCheckpointManager()->EndCommit(this); + } + m_checkpointCommitEnded = true; m_checkpointPhase = CheckpointPhase::NONE; - m_csn = CSNManager::INVALID_CSN; + m_csn = INVALID_CSN; m_occManager.CleanUp(); m_err = RC_OK; m_errIx = nullptr; m_flushDone = false; - m_internalTransactionId = GetTxnIdManager().GetNextId(); m_internalStmtCount = 0; + m_isSnapshotTaken = false; + m_hasIxColUpd = false; + if (GetGlobalConfiguration().m_enableRedoLog) { + m_internalTransactionId = GetTxnIdManager().GetNextId(); + } m_redoLog.Reset(); - GcSessionEnd(); + SetVisibleCSN(0); + if (!m_isRecoveryTxn) { + GcSessionEnd(); + } else { + GcSessionEndRecovery(); + } ClearErrorStack(); - m_accessMgr->ClearTableCache(); + m_dummyIndex->ClearKeyCache(); + m_accessMgr->ClearDummyTableCache(); + if (!m_isRecoveryTxn) { + m_accessMgr->ClearTableCache(); + } m_queryState.clear(); + m_subTxnManager.ResetSubTransactionMgr(); } -void TxnManager::UndoInserts() +void TxnManager::UndoLocalDMLChanges() { uint32_t rollbackCounter = 0; + bool hasTxnDDL = hasDDL(); TxnOrderedSet_t& OrderedSet = m_accessMgr->GetOrderedRowSet(); for (const auto& ra_pair : OrderedSet) { Access* ac = ra_pair.second; if (ac->m_type != AccessType::INS) { + if (unlikely(hasTxnDDL)) { + rollbackCounter++; + } continue; } rollbackCounter++; - RollbackInsert(ac); + // the index will de-allocated, so there is no need to remove data + if (ac->GetSentinel()->GetIndex()->GetIsCommited()) { + RollbackInsert(ac); + } else { + if (ac->m_secondaryDelKey != nullptr) { + ac->GetSentinel()->GetIndex()->DestroyKey(ac->m_secondaryDelKey); + ac->m_secondaryDelKey = nullptr; + } + } m_accessMgr->IncreaseTableStat(ac->GetTxnRow()->GetTable()); } @@ -381,53 +552,131 @@ void TxnManager::UndoInserts() // Row is local and was not inserted in the commit if (index_->GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { // Release local row to the GC!!!!! - ac->GetTxnRow()->GetTable()->DestroyRow(ac->GetTxnRow()); + Row* r = ac->GetTxnRow(); + r->GetTable()->DestroyRow(r); + ac->m_localInsertRow = nullptr; } rollbackCounter--; - if (rollbackCounter == 0) { - break; + } else { + if (unlikely(hasTxnDDL)) { + m_accessMgr->DestroyAccess(ac); + rollbackCounter--; + } + } + if (rollbackCounter == 0) { + break; + } + } +} + +void TxnManager::RollbackInsert(Access* ac) +{ + Sentinel* outputSen = nullptr; + RC rc = RC_OK; + Sentinel* sentinel = ac->GetSentinel(); + MOT::Index* index_ = sentinel->GetIndex(); + /* + * First we check if this is a new insert + * If so we can try remove it from the tree as it is not visible by any + * transaction We put the sentinels in the generic Queue + */ + if (ac->m_params.IsUpgradeInsert() == false) { + MOT_ASSERT(sentinel != nullptr); + // If the sentinel was never committed we may try to remove it + rc = sentinel->RefCountUpdate(DEC); + MOT_ASSERT(rc != RC::RC_INDEX_RETRY_INSERT); + if (rc == RC::RC_INDEX_DELETE) { + if (sentinel->IsDirty()) { + MOT::Key* pKey = m_key; + if (likely(ac->m_secondaryDelKey == nullptr)) { + MOT::Table* table = + (index_->GetTable()->IsTxnTable() ? index_->GetTable() : ac->GetTxnRow()->GetTable()); + // If refCount == 0, this sentinel passed GC barrier and can be removed from tree! + // Memory reclamation need to release the key from the primary sentinel back to the pool + m_key->InitKey(index_->GetKeyLength()); + index_->BuildKey(table, ac->GetTxnRow(), m_key); + } else { + pKey = ac->m_secondaryDelKey; + } + MOT_ASSERT(sentinel->GetCounter() == 0); +#ifdef MOT_DEBUG + Sentinel* curr_sentinel = index_->IndexReadHeader(pKey, GetThdId()); + MOT_ASSERT(curr_sentinel == sentinel); +#endif + outputSen = index_->IndexRemove(pKey, GetThdId()); + MOT_ASSERT(outputSen != nullptr); + MOT_ASSERT(outputSen->GetCounter() == 0); + if (index_->GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + (void)static_cast(outputSen)->GetGcInfo().RefCountUpdate(INC); + } + GcSessionRecordRcu(GC_QUEUE_TYPE::GENERIC_QUEUE, + index_->GetIndexId(), + outputSen, + nullptr, + Index::SentinelDtor, + SENTINEL_SIZE(index_)); + } else { + // If we rollback we can assume the top is deleted! + // We can never reach here since GC cleanup is + // blocked till will finish commit + MOT_ASSERT(false); + } + } + } else { + /* + * At this point we rollback the newly inserted row and responsible to reclaim + * the old deleted row synced with the other threads + * 1. Increase the gc_info and put the tombstone in the GC with the original CSN! + * 2. Try to reclaim the current insert using the refCount mechanism + * 3. SentinelDtor should support gcInfo synchronization + */ + if (ac->m_params.IsInsertOnDeletedRow()) { + rc = sentinel->RollBackUnique(); + if (ac->m_params.IsPrimarySentinel() == true) { + // If we are not the last owner skip the element + // If we are the last one (RC_INDEX_DELETE) Lets do the work of the original DELETE + if (rc == RC_INDEX_DELETE) { + // First Step register to gcInfo + (void)static_cast(ac->m_origSentinel)->GetGcInfo().RefCountUpdate(INC); + // Add the Sentinel to the GC - off load the work + Row* r = ac->GetGlobalVersion(); + MOT_ASSERT(r->IsRowDeleted() == true); + GcSessionRecordRcu(GC_QUEUE_TYPE::DELETE_QUEUE, + r->GetTable()->GetPrimaryIndex()->GetIndexId(), + r, + ac->m_origSentinel, + Row::DeleteRowDtor, + ROW_SIZE_FROM_POOL(r->GetTable())); + } + } else if (ac->m_params.IsUniqueIndex() == false) { + if (ac->m_params.IsIndexUpdate() == true) { + if (rc == RC_INDEX_DELETE) { + ReclaimAccessSecondaryDelKey(ac); + } + } + } + } else { + // Since the operation was on a committed version just decrease reference count + // Reference count must be greater then 1 after decrement! + rc = sentinel->RollBackUnique(); + if (ac->m_params.IsIndexUpdate() == true) { + if (rc == RC_INDEX_DELETE) { + ReclaimAccessSecondaryDelKey(ac); + } } } } } -RC TxnManager::RollbackInsert(Access* ac) +void TxnManager::ReclaimAccessSecondaryDelKey(Access* ac) { - Sentinel* outputSen = nullptr; - RC rc; - Sentinel* sentinel = ac->GetSentinel(); - MOT::Index* index_ = sentinel->GetIndex(); - - MOT_ASSERT(sentinel != nullptr); - rc = sentinel->RefCountUpdate(DEC, GetThdId()); - MOT_ASSERT(rc != RC::RC_INDEX_RETRY_INSERT); - if (rc == RC::RC_INDEX_DELETE) { - MaxKey m_key; - // Memory reclamation need to release the key from the primary sentinel back to the pool - m_key.InitKey(index_->GetKeyLength()); - index_->BuildKey(ac->GetTxnRow()->GetTable(), ac->GetTxnRow(), &m_key); - MOT_ASSERT(sentinel->GetCounter() == 0); -#ifdef MOT_DEBUG - Sentinel* curr_sentinel = index_->IndexReadHeader(&m_key, GetThdId()); - MOT_ASSERT(curr_sentinel == sentinel); -#endif - outputSen = index_->IndexRemove(&m_key, GetThdId()); - MOT_ASSERT(outputSen != nullptr); - MOT_ASSERT(outputSen->GetCounter() == 0); - - GcSessionRecordRcu(index_->GetIndexId(), outputSen, nullptr, Index::SentinelDtor, SENTINEL_SIZE(index_)); - // If we are the owner of the key and insert on top of a deleted row, - // lets check if we can reclaim the deleted row - if (ac->m_params.IsUpgradeInsert() and index_->IsPrimaryKey()) { - MOT_ASSERT(sentinel->GetData() != nullptr); - GcSessionRecordRcu(index_->GetIndexId(), - sentinel->GetData(), - nullptr, - Row::RowDtor, - ROW_SIZE_FROM_POOL(ac->GetTxnRow()->GetTable())); - } - } - return rc; + GcSessionRecordRcu(GC_QUEUE_TYPE::UPDATE_COLUMN_QUEUE, + ac->m_origSentinel->GetIndex()->GetIndexId(), + ac->m_origSentinel, + ac->m_secondaryDelKey, + Index::DeleteKeyDtor, + SENTINEL_SIZE(ac->m_origSentinel->GetIndex())); + ac->m_secondaryDelKey = nullptr; } void TxnManager::RollbackSecondaryIndexInsert(Index* index) @@ -445,7 +694,7 @@ void TxnManager::RollbackSecondaryIndexInsert(Index* index) // need to perform index clean-up! m_accessMgr->PubReleaseAccess(ac); } else { - it++; + (void)++it; } } } @@ -453,178 +702,198 @@ void TxnManager::RollbackSecondaryIndexInsert(Index* index) void TxnManager::RollbackDDLs() { // early exit - if (m_txnDdlAccess->Size() == 0) + if (m_txnDdlAccess->Size() == 0) { + ClearReservedChunks(); return; + } + // we need to generate notification also for each create table/index rollback to allow any key cleanup before row + // and key pools are deleted + for (int i = m_txnDdlAccess->Size() - 1; i >= 0; i--) { + MOT::Index* index = nullptr; + TxnTable* table = nullptr; + TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->Get(i); + switch (ddl_access->GetDDLAccessType()) { + case DDL_ACCESS_CREATE_TABLE: + table = (TxnTable*)ddl_access->GetEntry(); + MOTEngine::GetInstance()->NotifyDDLEvent( + table->GetTableExId(), MOT::DDL_ACCESS_CREATE_TABLE, TxnDDLPhase::TXN_DDL_PHASE_ROLLBACK); + break; + + case DDL_ACCESS_CREATE_INDEX: + index = (Index*)ddl_access->GetEntry(); + MOTEngine::GetInstance()->NotifyDDLEvent( + index->GetExtId(), MOT::DDL_ACCESS_CREATE_INDEX, TxnDDLPhase::TXN_DDL_PHASE_ROLLBACK); + break; + default: + break; + } + } + + // generate one more notification for entire transaction + MOTEngine::GetInstance()->NotifyDDLEvent(0, DDL_ACCESS_UNKNOWN, TxnDDLPhase::TXN_DDL_PHASE_ROLLBACK); + + m_txnDdlAccess->RollbackDDLChanges(this); // rollback DDLs in reverse order (avoid rolling back parent object before rolling back child) for (int i = m_txnDdlAccess->Size() - 1; i >= 0; i--) { MOT::Index* index = nullptr; - MOTIndexArr* indexArr = nullptr; - Table* table = nullptr; + TxnTable* table = nullptr; TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->Get(i); switch (ddl_access->GetDDLAccessType()) { case DDL_ACCESS_CREATE_TABLE: - table = (Table*)ddl_access->GetEntry(); + table = (TxnTable*)ddl_access->GetEntry(); MOT_LOG_INFO("Rollback of create table %s", table->GetLongTableName().c_str()); - table->DropImpl(); + table->GetOrigTable()->DropImpl(); RemoveTableFromStat(table); - if (table != nullptr) - delete table; + delete table->GetOrigTable(); break; case DDL_ACCESS_DROP_TABLE: - table = (Table*)ddl_access->GetEntry(); + table = (TxnTable*)ddl_access->GetEntry(); MOT_LOG_INFO("Rollback of drop table %s", table->GetLongTableName().c_str()); break; case DDL_ACCESS_TRUNCATE_TABLE: - indexArr = (MOTIndexArr*)ddl_access->GetEntry(); - table = indexArr->GetTable(); - table->WrLock(); - if (indexArr->GetNumIndexes() > 0) { - MOT_ASSERT(indexArr->GetNumIndexes() == table->GetNumIndexes()); - MOT_LOG_INFO("Rollback of truncate table %s", table->GetLongTableName().c_str()); - for (int idx = 0; idx < indexArr->GetNumIndexes(); idx++) { - uint16_t oldIx = indexArr->GetIndexIx(idx); - MOT::Index* oldIndex = indexArr->GetIndex(idx); - index = table->m_indexes[oldIx]; - table->m_indexes[oldIx] = oldIndex; - if (idx == 0) - table->m_primaryIndex = oldIndex; - else - table->m_secondaryIndexes[oldIndex->GetName()] = oldIndex; - GcManager::ClearIndexElements(index->GetIndexId()); - index->Truncate(true); - delete index; - } - } - table->ReplaceRowPool(indexArr->GetRowPool()); - table->Unlock(); - delete indexArr; break; case DDL_ACCESS_CREATE_INDEX: - index = (Index*)ddl_access->GetEntry(); - table = index->GetTable(); - MOT_LOG_INFO("Rollback of create index %s for table %s", - index->GetName().c_str(), - table->GetLongTableName().c_str()); - table->WrLock(); - if (index->IsPrimaryKey()) { - table->DecIndexColumnUsage(index); - table->SetPrimaryIndex(nullptr); - table->DeleteIndex(index); - } else { - table->RemoveSecondaryIndex(index, this); - } - table->Unlock(); break; case DDL_ACCESS_DROP_INDEX: index = (Index*)ddl_access->GetEntry(); - table = index->GetTable(); - MOT_LOG_INFO("Rollback of drop index %s for table %s", - index->GetName().c_str(), - table->GetLongTableName().c_str()); - table->WrLock(); - if (index->IsPrimaryKey()) { - table->IncIndexColumnUsage(index); - table->SetPrimaryIndex(index); - } else { - table->AddSecondaryIndexToMetaData(index); + if (index->GetTable()->IsTxnTable()) { + index->GetTable()->DeleteIndex(index); } - table->Unlock(); break; + case DDL_ACCESS_ADD_COLUMN: { + DDLAlterTableAddDropColumn* alter = (DDLAlterTableAddDropColumn*)ddl_access->GetEntry(); + if (alter->m_column != nullptr) { + delete alter->m_column; + } + delete alter; + break; + } + case DDL_ACCESS_DROP_COLUMN: { + DDLAlterTableAddDropColumn* alter = (DDLAlterTableAddDropColumn*)ddl_access->GetEntry(); + if (alter->m_column != nullptr) { + delete alter->m_column; + } + delete alter; + break; + } + case DDL_ACCESS_RENAME_COLUMN: { + DDLAlterTableRenameColumn* alter = (DDLAlterTableRenameColumn*)ddl_access->GetEntry(); + delete alter; + break; + } default: break; } } + ClearReservedChunks(); } -void TxnManager::CleanDDLChanges() +void TxnManager::ApplyDDLChanges() { // early exit - if (m_txnDdlAccess->Size() == 0) + if (m_txnDdlAccess->Size() == 0) { + ClearReservedChunks(); return; + } + + MOTEngine::GetInstance()->NotifyDDLEvent(0, DDL_ACCESS_UNKNOWN, MOT::TxnDDLPhase::TXN_DDL_PHASE_COMMIT); MOT::Index* index = nullptr; - MOTIndexArr* indexArr = nullptr; - Table* table = nullptr; + TxnTable* table = nullptr; for (uint16_t i = 0; i < m_txnDdlAccess->Size(); i++) { TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->Get(i); switch (ddl_access->GetDDLAccessType()) { - case DDL_ACCESS_CREATE_TABLE: - GetTableManager()->AddTable((Table*)ddl_access->GetEntry()); - break; - case DDL_ACCESS_DROP_TABLE: - GetTableManager()->DropTable((Table*)ddl_access->GetEntry(), m_sessionContext); - break; - case DDL_ACCESS_TRUNCATE_TABLE: - indexArr = (MOTIndexArr*)ddl_access->GetEntry(); - table = indexArr->GetTable(); - if (indexArr->GetNumIndexes() > 0) { - table->m_rowCount = 0; - for (int i = 0; i < indexArr->GetNumIndexes(); i++) { - index = indexArr->GetIndex(i); - table->DeleteIndex(index); - } - } - table->FreeObjectPool(indexArr->GetRowPool()); - delete indexArr; - break; - case DDL_ACCESS_CREATE_INDEX: - index = (Index*)ddl_access->GetEntry(); - table = index->GetTable(); - index->SetIsCommited(true); - break; case DDL_ACCESS_DROP_INDEX: index = (Index*)ddl_access->GetEntry(); - table = index->GetTable(); - table->DeleteIndex(index); + if (index->GetTable()->IsTxnTable()) { + index->GetTable()->DeleteIndex(index); + } break; + case DDL_ACCESS_ADD_COLUMN: { + DDLAlterTableAddDropColumn* alterAdd = (DDLAlterTableAddDropColumn*)ddl_access->GetEntry(); + if (alterAdd->m_column != nullptr) { + delete alterAdd->m_column; + } + delete alterAdd; + break; + } + case DDL_ACCESS_DROP_COLUMN: { + DDLAlterTableAddDropColumn* alterDrop = (DDLAlterTableAddDropColumn*)ddl_access->GetEntry(); + if (alterDrop->m_column != nullptr) { + delete alterDrop->m_column; + } + delete alterDrop; + break; + } + case DDL_ACCESS_RENAME_COLUMN: { + DDLAlterTableRenameColumn* alterRename = (DDLAlterTableRenameColumn*)ddl_access->GetEntry(); + delete alterRename; + break; + } default: break; } } -} -Row* TxnManager::RemoveKeyFromIndex(Row* row, Sentinel* sentinel) -{ - Table* table = row->GetTable(); + m_txnDdlAccess->ApplyDDLChanges(this); - Row* outputRow = nullptr; - if (sentinel->GetStable() == nullptr) { - outputRow = table->RemoveKeyFromIndex(row, sentinel, m_threadId, GetGcSession()); - } else { - // Checkpoint works on primary-sentinel only! - if (sentinel->IsPrimaryIndex() == false) { - outputRow = table->RemoveKeyFromIndex(row, sentinel, m_threadId, GetGcSession()); + for (uint16_t i = 0; i < m_txnDdlAccess->Size(); i++) { + TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->Get(i); + if (ddl_access->GetDDLAccessType() == DDL_ACCESS_CREATE_TABLE) { + table = (TxnTable*)ddl_access->GetEntry(); + if (table != nullptr && !table->IsDropped()) { + (void)GetTableManager()->AddTable(table->GetOrigTable()); + } + } else if (ddl_access->GetDDLAccessType() == DDL_ACCESS_DROP_TABLE) { + table = (TxnTable*)ddl_access->GetEntry(); + table->RollbackDDLChanges(this); + (void)GetTableManager()->DropTable(table->GetOrigTable(), this); } - outputRow = row; } - return outputRow; -} - -RC TxnManager::RowDel() -{ - return UpdateLastRowState(AccessType::DEL); + ClearReservedChunks(); } // Use this function when we have only the key! // Not Used with FDW! -Row* TxnManager::RowLookupByKey(Table* const& table, const AccessType type, Key* const currentKey) +Row* TxnManager::RowLookupByKey(Table* const& table, const AccessType type, Key* const currentKey, RC& rc) { - RC rc = RC_OK; - Row* originalRow = nullptr; + rc = RC_OK; Sentinel* pSentinel = nullptr; - table->FindRow(currentKey, pSentinel, GetThdId()); + (void)table->FindRow(currentKey, pSentinel, GetThdId()); if (pSentinel == nullptr) { - MOT_LOG_DEBUG("Cannot find key:%" PRIu64 " from table:%s", m_key, table->GetLongTableName().c_str()); + MOT_LOG_DEBUG("Cannot find key:%" PRIu64 " from table:%s Visible CSN %lu", + m_key, + table->GetLongTableName().c_str(), + GetVisibleCSN()); return nullptr; } else { return RowLookup(type, pSentinel, rc); } } -TxnManager::TxnManager(SessionContext* session_context) - : m_latestEpoch(~uint64_t(0)), +RC TxnManager::SetSnapshot() +{ + RC rc = RC_OK; + if (!GetSnapshotStatus()) { + if (m_gcSession->IsGcSnapshotTaken() == true) { + SetVisibleCSN(MOTEngine::GetInstance()->GetCurrentCSN()); + } else { + // Need to Guaranty GC work on latest snapshot + rc = GcSessionStart(); + SetVisibleCSN(m_gcSession->GetCurrentEpoch()); + } + SetSnapshotStatus(true); + } + if (IsRecoveryTxn()) { + SetVisibleCSN(static_cast(-1)); + } + return rc; +} + +TxnManager::TxnManager(SessionContext* session_context, bool global /* = false */) + : m_visibleCSN(0), m_threadId((uint64_t)-1), m_connectionId((uint64_t)-1), m_sessionContext(session_context), @@ -633,20 +902,33 @@ TxnManager::TxnManager(SessionContext* session_context) m_gcSession(nullptr), m_checkpointPhase(CheckpointPhase::NONE), m_checkpointNABit(false), - m_csn(CSNManager::INVALID_CSN), + m_checkpointCommitEnded(false), + m_csn(INVALID_CSN), m_transactionId(INVALID_TRANSACTION_ID), m_replayLsn(0), m_surrogateGen(), m_flushDone(false), + m_internalTransactionId(0), m_internalStmtCount(0), + m_state(TxnState::TXN_START), m_isolationLevel(READ_COMMITED), + m_isSnapshotTaken(false), + m_isTxnAborted(false), + m_hasCommitedSubTxnDDL(false), + m_isCrossEngineTxn(false), + m_isRecoveryTxn(false), + m_hasIxColUpd(false), + m_key(nullptr), m_isLightSession(false), m_errIx(nullptr), - m_err(RC_OK) + m_err(RC_OK), + m_reservedChunks(0), + m_global(global), + m_dummyIndex(nullptr) { - m_key = nullptr; - m_state = TxnState::TXN_START; - m_internalTransactionId = GetTxnIdManager().GetNextId(); + if (GetGlobalConfiguration().m_enableRedoLog) { + m_internalTransactionId = GetTxnIdManager().GetNextId(); + } } TxnManager::~TxnManager() @@ -657,61 +939,88 @@ TxnManager::~TxnManager() MOT_LOG_DEBUG("txn_man::~txn_man - memory pools released for thread_id=%lu", m_threadId); + if (m_dummyIndex != nullptr) { + delete m_dummyIndex; + } if (m_gcSession != nullptr) { m_gcSession->GcCleanAll(); m_gcSession->RemoveFromGcList(m_gcSession); m_gcSession->~GcManager(); - MemSessionFree(m_gcSession); + MemFree(m_gcSession, m_global); + m_gcSession = nullptr; } + + MemFreeObject(m_accessMgr, m_global); + m_accessMgr = nullptr; delete m_key; delete m_txnDdlAccess; + m_errIx = nullptr; + m_sessionContext = nullptr; } -bool TxnManager::Init(uint64_t _thread_id, uint64_t connection_id, bool isLightTxn) +bool TxnManager::Init(uint64_t threadId, uint64_t connectionId, bool isRecoveryTxn, bool isLightTxn) { - this->m_threadId = _thread_id; - this->m_connectionId = connection_id; + m_threadId = threadId; + m_connectionId = connectionId; m_isLightSession = isLightTxn; + m_isRecoveryTxn = isRecoveryTxn; - if (!m_occManager.Init()) + m_dummyIndex = new (std::nothrow) DummyIndex(); + if (m_dummyIndex == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Transaction", "Failed to allocate memory for dummy index"); return false; + } + if (!m_dummyIndex->Init(m_global)) { + return false; + } m_txnDdlAccess = new (std::nothrow) TxnDDLAccess(this); - if (!m_txnDdlAccess) { + if (m_txnDdlAccess == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Initialize Transaction", "Failed to allocate memory for DDL access data"); return false; } m_txnDdlAccess->Init(); // make node-local allocations - if (isLightTxn == false) { - m_accessMgr = MemSessionAllocAlignedObjectPtr(L1_CACHE_LINE); + if (!isLightTxn) { + m_accessMgr = MemAllocAlignedObject(L1_CACHE_LINE, m_global); - if (!m_accessMgr->Init(this)) + if (!m_accessMgr->Init(this)) { return false; + } - m_surrogateGen = GetSurrogateKeyManager()->GetSurrogateSlot(connection_id); + m_surrogateGen = GetSurrogateKeyManager()->GetSurrogateSlot(connectionId); } - m_gcSession = GcManager::Make(GcManager::GC_MAIN, _thread_id); - if (!m_gcSession) + if (!m_subTxnManager.Init(this)) { return false; + } + if (isRecoveryTxn) { + m_gcSession = GcManager::Make(GcManager::GC_TYPE::GC_RECOVERY, threadId, m_global); + } else { + m_gcSession = GcManager::Make(GcManager::GC_TYPE::GC_MAIN, threadId, m_global); + } + if (!m_gcSession) { + return false; + } - if (!m_redoLog.Init()) + if (!m_redoLog.Init()) { return false; + } static const TxnValidation validation_lock = GetGlobalConfiguration().m_validationLock; if (m_key == nullptr) { MOT_LOG_DEBUG("Init Key"); m_key = new (std::nothrow) MaxKey(MAX_KEY_SIZE); - if (m_key == nullptr) + if (m_key == nullptr) { MOTAbort(); + } } m_occManager.SetPreAbort(GetGlobalConfiguration().m_preAbort); - if (validation_lock == TxnValidation::TXN_VALIDATION_NO_WAIT) + if (validation_lock == TxnValidation::TXN_VALIDATION_NO_WAIT) { m_occManager.SetValidationNoWait(true); - else if (validation_lock == TxnValidation::TXN_VALIDATION_WAITING) { + } else if (validation_lock == TxnValidation::TXN_VALIDATION_WAITING) { m_occManager.SetValidationNoWait(false); } else { MOT_ASSERT(false); @@ -720,40 +1029,59 @@ bool TxnManager::Init(uint64_t _thread_id, uint64_t connection_id, bool isLightT return true; } -RC TxnManager::OverwriteRow(Row* updatedRow, BitmapSet& modifiedColumns) +RC TxnManager::OverwriteRow(Row* updatedRow, const BitmapSet& modifiedColumns) { - if (updatedRow == nullptr) + if (updatedRow == nullptr) { return RC_ERROR; + } + SetTxnOper(SubTxnOperType::SUB_TXN_DML); Access* access = m_accessMgr->GetLastAccess(); if (access->m_type == AccessType::WR) { access->m_modifiedColumns |= modifiedColumns; - access->m_stmtCount = GetStmtCount(); } + access->IncreaseOps(); + access->m_stmtCount = GetStmtCount(); return RC_OK; } +RC TxnManager::UpdateRow(const BitmapSet& modifiedColumns, TxnIxColUpdate* colUpd) +{ + SetTxnOper(SubTxnOperType::SUB_TXN_DML); + Access* access = m_accessMgr->GetLastAccess(); + if (access->m_type == AccessType::WR) { + access->m_modifiedColumns |= modifiedColumns; + } + access->m_params.SetIndexUpdate(); + access->IncreaseOps(); + access->m_stmtCount = GetStmtCount(); + m_hasIxColUpd = true; + if (colUpd->m_hasIxColUpd == UpdateIndexColumnType::UPDATE_COLUMN_PRIMARY) { + return m_accessMgr->GeneratePrimaryIndexUpdate(access, colUpd); + } else { + return m_accessMgr->GenerateSecondaryIndexUpdate(access, colUpd); + } +} + void TxnManager::SetTxnState(TxnState envelopeState) { m_state = envelopeState; } -void TxnManager::SetTxnIsoLevel(int envelopeIsoLevel) +void TxnManager::GcSessionRecordRcu(GC_QUEUE_TYPE m_type, uint32_t index_id, void* object_ptr, void* object_pool, + DestroyValueCbFunc cb, uint32_t obj_size, uint64_t csn) { - m_isolationLevel = envelopeIsoLevel; -} - -void TxnManager::GcSessionRecordRcu( - uint32_t index_id, void* object_ptr, void* object_pool, DestroyValueCbFunc cb, uint32_t obj_size) -{ - return m_gcSession->GcRecordObject(index_id, object_ptr, object_pool, cb, obj_size); + return m_gcSession->GcRecordObject(m_type, index_id, object_ptr, object_pool, cb, obj_size, csn); } TxnInsertAction::~TxnInsertAction() { if (m_manager != nullptr && m_insertSet != nullptr) { - MemSessionFree(m_insertSet); + MemFree(m_insertSet, m_manager->m_global); } + + m_insertSet = nullptr; + m_manager = nullptr; } bool TxnInsertAction::Init(TxnManager* _manager) @@ -766,7 +1094,7 @@ bool TxnInsertAction::Init(TxnManager* _manager) m_insertSetSize = 0; m_insertArraySize = INSERT_ARRAY_DEFAULT_SIZE; - void* ptr = MemSessionAlloc(sizeof(InsItem) * m_insertArraySize); + void* ptr = MemAlloc(sizeof(InsItem) * m_insertArraySize, m_manager->m_global); if (ptr == nullptr) return false; m_insertSet = reinterpret_cast(ptr); @@ -778,7 +1106,6 @@ bool TxnInsertAction::Init(TxnManager* _manager) void TxnInsertAction::ReportError(RC rc, InsItem* currentItem) { - switch (rc) { case RC_OK: break; @@ -786,7 +1113,7 @@ void TxnInsertAction::ReportError(RC rc, InsItem* currentItem) // set error m_manager->m_err = RC_UNIQUE_VIOLATION; m_manager->m_errIx = currentItem->m_index; - m_manager->m_errIx->BuildErrorMsg(currentItem->m_row->GetTable(), + m_manager->m_errIx->BuildErrorMsg(currentItem->m_index->GetTable(), currentItem->m_row, m_manager->m_errMsgBuf, sizeof(m_manager->m_errMsgBuf)); @@ -803,6 +1130,30 @@ void TxnInsertAction::ReportError(RC rc, InsItem* currentItem) } } +void TxnInsertAction::CleanupInsertReclaimKey(Row* row, Sentinel* sentinel) +{ + // Memory reclamation need to release the key from the primary sentinel back to the pool + MOT_ASSERT(sentinel->GetCounter() == 0); + Table* table = row->GetTable(); + MOT::Index* index = sentinel->GetIndex(); + Key* localKey = m_manager->GetLocalKey(); + localKey->InitKey(index->GetKeyLength()); + index->BuildKey(table, row, localKey); + Sentinel* outputSen = index->IndexRemove(localKey, m_manager->GetThdId()); + MOT_ASSERT(outputSen != nullptr); + MOT_ASSERT(outputSen->GetCounter() == 0); + if (index->GetIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + (void)static_cast(outputSen)->GetGcInfo().RefCountUpdate(INC); + } + m_manager->GcSessionRecordRcu(GC_QUEUE_TYPE::GENERIC_QUEUE, + index->GetIndexId(), + outputSen, + nullptr, + Index::SentinelDtor, + SENTINEL_SIZE(index)); + m_manager->m_accessMgr->IncreaseTableStat(table); +} + void TxnInsertAction::CleanupOptimisticInsert( InsItem* currentItem, Sentinel* pIndexInsertResult, bool isInserted, bool isMappedToCache) { @@ -810,37 +1161,87 @@ void TxnInsertAction::CleanupOptimisticInsert( // Clean first Object! - wither primary or secondary! // Return Local Row to pull for PI Table* table = currentItem->m_row->GetTable(); - if (currentItem->getIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { - table->DestroyRow(currentItem->m_row); - } - if (isInserted == true) { - if (isMappedToCache == false) { - RC rc = pIndexInsertResult->RefCountUpdate(DEC, m_manager->GetThdId()); - MOT::Index* index_ = pIndexInsertResult->GetIndex(); + if (isInserted) { + if (!isMappedToCache) { + RC rc = pIndexInsertResult->RefCountUpdate(DEC); if (rc == RC::RC_INDEX_DELETE) { - // Memory reclamation need to release the key from the primary sentinel back to the pool - MOT_ASSERT(pIndexInsertResult->GetCounter() == 0); - Sentinel* outputSen = index_->IndexRemove(currentItem->m_key, m_manager->GetThdId()); - MOT_ASSERT(outputSen != nullptr); - MOT_ASSERT(outputSen->GetCounter() == 0); - - m_manager->GcSessionRecordRcu( - index_->GetIndexId(), outputSen, nullptr, Index::SentinelDtor, SENTINEL_SIZE(index_)); - m_manager->m_accessMgr->IncreaseTableStat(table); + CleanupInsertReclaimKey(currentItem->m_row, pIndexInsertResult); } } } + if (currentItem->getIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + table->DestroyRow(currentItem->m_row); + } } -RC TxnInsertAction::ExecuteOptimisticInsert(Row* row) +RC TxnInsertAction::AddInsertToLocalAccess( + Row* row, InsItem* currentItem, Sentinel* pIndexInsertResult, bool& isMappedToCache) { - Sentinel* pIndexInsertResult = nullptr; + RC rc = RC_OK; Row* accessRow = nullptr; + TxnAccess* accessMgr = m_manager->m_accessMgr; + isMappedToCache = false; + if (pIndexInsertResult->IsCommited() == true) { + // Lets check and see if we deleted the row + rc = accessMgr->CheckDuplicateInsert(pIndexInsertResult); + switch (rc) { + case RC_LOCAL_ROW_DELETED: + // promote delete to Insert! + // In this case the sentinel is committed and the previous scenario was delete + // Insert succeeded Sentinel was not committed before! + // At this point the row is not in the cache and can be mapped! + MOT_ASSERT(currentItem->m_index->GetUnique() == true); + // fall through + case RC_LOCAL_ROW_NOT_FOUND: + // Header is committed + accessRow = accessMgr->AddInsertToLocalAccess(pIndexInsertResult, row, rc, true); + if (accessRow == nullptr) { + ReportError(rc, currentItem); + return rc; + } + isMappedToCache = true; + break; + case RC_LOCAL_ROW_FOUND: + // Found But not deleted = self duplicated! + ReportError(RC_UNIQUE_VIOLATION, currentItem); + return RC_UNIQUE_VIOLATION; + default: + return rc; + } + } else { + // tag all the sentinels the insert metadata + MOT_ASSERT(pIndexInsertResult->GetCounter() != 0); + // At this point the sentinel must be either committed/not-committed + if (unlikely(pIndexInsertResult->IsCommited())) { + ReportError(RC_UNIQUE_VIOLATION, currentItem); + return RC_UNIQUE_VIOLATION; + } + + // Insert succeeded Sentinel was not committed before! + accessRow = accessMgr->AddInsertToLocalAccess(pIndexInsertResult, row, rc); + if (accessRow == nullptr) { + ReportError(rc, currentItem); + return rc; + } + isMappedToCache = true; + } + return rc; +} + +RC TxnInsertAction::ExecuteOptimisticInsert(Row* row, Key* updateColumnKey) +{ + MaxKey key; + Key* tKey = &key; + Sentinel* pIndexInsertResult = nullptr; RC rc = RC_OK; bool isInserted = true; bool isMappedToCache = false; - auto currentItem = BeginCursor(); + Table* table = row->GetTable(); + Table* txnTable = m_manager->GetTxnTable(table->GetTableExId()); + if (txnTable != nullptr) { + table = txnTable; + } /* * 1.Add all sentinels to the Access SET type P_SENTINEL or S_SENTINEL @@ -853,64 +1254,34 @@ RC TxnInsertAction::ExecuteOptimisticInsert(Row* row) while (currentItem != EndCursor()) { isInserted = true; isMappedToCache = false; - bool res = reinterpret_cast(currentItem->m_index) - ->IndexInsert(pIndexInsertResult, currentItem->m_key, m_manager->GetThdId(), rc); + if (unlikely(updateColumnKey != nullptr)) { + tKey = updateColumnKey; + } else { + tKey->InitKey(currentItem->m_index->GetKeyLength()); + currentItem->m_index->BuildKey(table, row, tKey); + } + (void)reinterpret_cast(currentItem->m_index) + ->IndexInsert(pIndexInsertResult, tKey, m_manager->GetThdId(), rc, m_manager->IsRecoveryTxn()); if (unlikely(rc == RC_MEMORY_ALLOCATION_ERROR)) { ReportError(rc); // Failed on Memory isInserted = false; goto end; } else { - if (currentItem->getIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { - row->SetAbsentRow(); - row->SetPrimarySentinel(pIndexInsertResult); - MOT_ASSERT(row->IsAbsentRow()); - } - } - if (pIndexInsertResult->IsCommited() == true) { - // Lets check and see if we deleted the row - Row* local_row = nullptr; - rc = m_manager->AccessLookup(RD, pIndexInsertResult, local_row); - switch (rc) { - case RC_LOCAL_ROW_DELETED: - // promote delete to Insert! - // In this case the sentinel is committed and the previous scenario was delete - // Insert succeeded Sentinel was not committed before! - // At this point the row is not in the cache and can be mapped! - MOT_ASSERT(currentItem->m_index->GetUnique() == true); - accessRow = m_manager->m_accessMgr->AddInsertToLocalAccess(pIndexInsertResult, row, rc, true); - if (accessRow != nullptr) { - isMappedToCache = true; - } - ReportError(rc, currentItem); - break; - case RC_LOCAL_ROW_NOT_FOUND: - // Header is committed - case RC_LOCAL_ROW_FOUND: - // Found But not deleted = self duplicated! - ReportError(RC_UNIQUE_VIOLATION, currentItem); - rc = RC_UNIQUE_VIOLATION; - goto end; - default: - goto end; - } - } else if (res == true or pIndexInsertResult->IsCommited() == false) { - // tag all the sentinels the insert metadata - MOT_ASSERT(pIndexInsertResult->GetCounter() != 0); - // Reuse the row connected to header - if (unlikely(pIndexInsertResult->GetData() != nullptr)) { - if (pIndexInsertResult->IsCommited() == false) { - accessRow = m_manager->m_accessMgr->AddInsertToLocalAccess(pIndexInsertResult, row, rc, true); - } - } else { - // Insert succeeded Sentinel was not committed before! - accessRow = m_manager->m_accessMgr->AddInsertToLocalAccess(pIndexInsertResult, row, rc); - } - if (accessRow == nullptr) { - ReportError(rc, currentItem); + if (rc == RC_UNIQUE_VIOLATION) { + ReportError(RC_UNIQUE_VIOLATION, currentItem); + isInserted = false; goto end; } - isMappedToCache = true; + if (currentItem->getIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + row->SetPrimarySentinel(pIndexInsertResult); + } + } + + rc = AddInsertToLocalAccess(row, currentItem, pIndexInsertResult, isMappedToCache); + if (rc != RC_OK) { + // ReportError already done inside AddInsertToLocalAccess(). + goto end; } ++currentItem; } @@ -920,85 +1291,210 @@ end: CleanupOptimisticInsert(currentItem, pIndexInsertResult, isInserted, isMappedToCache); } - // Clean keys - currentItem = BeginCursor(); - while (currentItem < EndCursor()) { - m_manager->DestroyTxnKey(currentItem->m_key); - currentItem++; - } - // Clear the current set size; m_insertSetSize = 0; return rc; } +void TxnInsertAction::CleanupRecoveryOCCInsert(Row* row, std::vector& sentinels) +{ + TxnOrderedSet_t& orderedSet = m_manager->m_accessMgr->GetOrderedRowSet(); + for (auto s : sentinels) { + if (s->RefCountUpdate(DEC) == RC::RC_INDEX_DELETE) { + CleanupInsertReclaimKey(row, s); + } + // Remove the access if mapped + auto search = orderedSet.find(s); + if (search != orderedSet.end()) { + Access* ac = (*search).second; + if (ac->m_params.IsUpgradeInsert() == true and ac->m_params.IsInsertOnDeletedRow() == false) { + ac->m_type = DEL; + ac->m_params.UnsetUpgradeInsert(); + ac->m_localInsertRow = nullptr; + if (ac->GetSentinel()->GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + S_SentinelNodePool* sentinelObjectPool = m_manager->m_accessMgr->GetSentinelObjectPool(); + auto object = sentinelObjectPool->find(ac->m_origSentinel->GetIndex()); + PrimarySentinelNode* node = object->second; + (void)sentinelObjectPool->erase(object); + ac->m_origSentinel->GetIndex()->SentinelNodeRelease(node); + } + continue; + } + (void)orderedSet.erase(search); + // need to perform index clean-up! + m_manager->m_accessMgr->PubReleaseAccess(ac); + } + } +} + +RC TxnInsertAction::ExecuteRecoveryOCCInsert(Row* row) +{ + MaxKey key; + Sentinel* pIndexInsertResult = nullptr; + RC rc = RC_OK; + std::vector sentinels; + auto currentItem = BeginCursor(); + + /* + * 1.Add all sentinels to the Access SET type P_SENTINEL or S_SENTINEL + * 2.IF Sentinel is committed abort! + * 3.We perform lookup directly on sentinels + * 4.We do not attach the row to the index,we map it to the access + * 5.We can release the row in the case of early abort only for Primary + * 6.No need to copy the row to the local_access + */ + while (currentItem != EndCursor()) { + key.InitKey(currentItem->m_index->GetKeyLength()); + currentItem->m_index->BuildKey(row->GetTable(), row, &key); + (void)reinterpret_cast(currentItem->m_index) + ->IndexInsert(pIndexInsertResult, &key, m_manager->GetThdId(), rc, m_manager->IsRecoveryTxn()); + if (unlikely(rc == RC_MEMORY_ALLOCATION_ERROR)) { + // Failed on Memory + ReportError(rc); + goto end; + } else { + if (rc == RC_UNIQUE_VIOLATION) { + ReportError(RC_UNIQUE_VIOLATION, currentItem); + goto end; + } + if (currentItem->getIndexOrder() == IndexOrder::INDEX_ORDER_PRIMARY) { + row->SetPrimarySentinel(pIndexInsertResult); + } + sentinels.push_back(pIndexInsertResult); + } + + // Wait for previous transaction to finish + while (pIndexInsertResult->IsLocked() == true) { + PAUSE; + } + + bool isMappedToCache = false; + rc = AddInsertToLocalAccess(row, currentItem, pIndexInsertResult, isMappedToCache); + if (rc != RC_OK) { + // ReportError already done inside AddInsertToLocalAccess(). + goto end; + } + ++currentItem; + } + +end: + if (rc != RC_OK) { + CleanupRecoveryOCCInsert(row, sentinels); + } else { + // Clear the current set size; + m_insertSetSize = 0; + } + return rc; +} + bool TxnInsertAction::ReallocInsertSet() { bool rc = true; - uint64_t new_array_size = (uint64_t)m_insertArraySize * INSERT_ARRAY_EXTEND_FACTOR; - void* ptr = MemSessionAlloc(sizeof(InsItem) * new_array_size); - if (__builtin_expect(ptr == nullptr, 0)) { - MOT_LOG_ERROR("%s: failed", __func__); - MOT_ASSERT(ptr != nullptr); + uint64_t newArraySize = (uint64_t)m_insertArraySize * INSERT_ARRAY_EXTEND_FACTOR; + // first check for overflow + if (newArraySize >= UINT32_MAX) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, + "Transaction Processing", + "Failed to reallocate insert set: Insert set size overflow"); return false; } - errno_t erc = memset_s(ptr, sizeof(InsItem) * new_array_size, 0, sizeof(InsItem) * new_array_size); + uint64_t allocSize = sizeof(InsItem) * newArraySize; + void* ptr = MemAlloc(allocSize, m_manager->m_global); + if (__builtin_expect(ptr == nullptr, 0)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Transaction Processing", + "Failed to reallocate insert set: Failed to allocate %" PRIu64 " bytes for %" PRIu64 " insert items", + allocSize, + newArraySize); + return false; + } + errno_t erc = memset_s(ptr, allocSize, 0, allocSize); securec_check(erc, "\0", "\0"); - erc = memcpy_s(ptr, sizeof(InsItem) * new_array_size, m_insertSet, sizeof(InsItem) * m_insertArraySize); + erc = memcpy_s(ptr, allocSize, static_cast(m_insertSet), sizeof(InsItem) * m_insertArraySize); securec_check(erc, "\0", "\0"); - MemSessionFree(m_insertSet); - m_insertSet = reinterpret_cast(ptr); - m_insertArraySize = new_array_size; + MemFree(m_insertSet, m_manager->m_global); + m_insertSet = static_cast(ptr); + m_insertArraySize = static_cast(newArraySize); // safe cast, limit for type uint32_t was checked return rc; } void TxnInsertAction::ShrinkInsertSet() { uint64_t new_array_size = INSERT_ARRAY_DEFAULT_SIZE; - void* ptr = MemSessionAlloc(sizeof(InsItem) * new_array_size); + void* ptr = MemAlloc(sizeof(InsItem) * new_array_size, m_manager->m_global); if (__builtin_expect(ptr == nullptr, 0)) { MOT_LOG_ERROR("%s: failed", __func__); return; } - errno_t erc = memcpy_s(ptr, sizeof(InsItem) * new_array_size, m_insertSet, sizeof(InsItem) * new_array_size); + errno_t erc = memcpy_s( + ptr, sizeof(InsItem) * new_array_size, static_cast(m_insertSet), sizeof(InsItem) * new_array_size); securec_check(erc, "\0", "\0"); - MemSessionFree(m_insertSet); - m_insertSet = reinterpret_cast(ptr); + MemFree(m_insertSet, m_manager->m_global); + m_insertSet = static_cast(ptr); m_insertArraySize = new_array_size; } /******************** DDL SUPPORT ********************/ Table* TxnManager::GetTableByExternalId(uint64_t id) { - TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->GetByOid(id); - if (ddl_access != nullptr) { - switch (ddl_access->GetDDLAccessType()) { - case DDL_ACCESS_CREATE_TABLE: - return (Table*)ddl_access->GetEntry(); - case DDL_ACCESS_TRUNCATE_TABLE: - return ((MOTIndexArr*)ddl_access->GetEntry())->GetTable(); - case DDL_ACCESS_DROP_TABLE: - return nullptr; - default: - break; - } - } + Table* table = GetTxnTable(id); - return GetTableManager()->GetTableByExternal(id); + if (table == nullptr) { + return GetTableManager()->GetTableByExternal(id); + } else { + return table; + } } -Index* TxnManager::GetIndexByExternalId(uint64_t table_id, uint64_t index_id) +Index* TxnManager::GetIndexByExternalId(uint64_t tableId, uint64_t indexId) { - Table* table = GetTableByExternalId(table_id); + Table* table = GetTableByExternalId(tableId); if (table == nullptr) { return nullptr; } else { - return table->GetIndexByExtId(index_id); + return table->GetIndexByExtId(indexId); } } +Table* TxnManager::GetTxnTable(uint64_t tableId) +{ + return m_txnDdlAccess->GetTxnTable(tableId); +} + +RC TxnManager::CreateTxnTable(Table* table, TxnTable*& txnTable) +{ + if (table->IsTxnTable()) { + txnTable = static_cast(table); + return RC_OK; + } + + txnTable = (TxnTable*)GetTxnTable(table->GetTableExId()); + if (txnTable != nullptr) { + return RC_OK; + } + + TxnTable* tab = new (std::nothrow) MOT::TxnTable(table); + if (tab == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create TxnTable", "Failed to allocate memory for DDL Access object"); + return RC_MEMORY_ALLOCATION_ERROR; + } + if (!tab->InitTxnTable()) { + delete tab; + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Init TxnTable", "Failed to allocate memory for DDL Access object"); + return RC_MEMORY_ALLOCATION_ERROR; + } + txnTable = tab; + m_txnDdlAccess->AddTxnTable(txnTable); + return RC_OK; +} + RC TxnManager::CreateTable(Table* table) { + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); size_t serializeRedoSize = table->SerializeRedoSize(); if (serializeRedoSize > RedoLogWriter::REDO_MAX_TABLE_SERIALIZE_SIZE) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -1009,144 +1505,190 @@ RC TxnManager::CreateTable(Table* table) return RC_ERROR; } + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + txnTable->SetIsNew(); + TxnDDLAccess::DDLAccess* ddl_access = - new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_CREATE_TABLE, (void*)table); + new (std::nothrow) TxnDDLAccess::DDLAccess(txnTable->GetTableExId(), DDL_ACCESS_CREATE_TABLE, (void*)txnTable); if (ddl_access == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create Table", "Failed to allocate memory for DDL Access object"); return RC_MEMORY_ALLOCATION_ERROR; } - m_txnDdlAccess->Add(ddl_access); + (void)m_txnDdlAccess->Add(ddl_access); return RC_OK; } RC TxnManager::DropTable(Table* table) { - RC res = RC_OK; + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); // we allocate all memory before action takes place, so that if memory allocation fails, we can report error safely TxnDDLAccess::DDLAccess* new_ddl_access = - new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_DROP_TABLE, (void*)table); + new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_DROP_TABLE, (void*)txnTable); if (new_ddl_access == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Drop Table", "Failed to allocate DDL Access object"); return RC_MEMORY_ALLOCATION_ERROR; } if (!m_isLightSession) { + rowAddrMap_t rows; TxnOrderedSet_t& access_row_set = m_accessMgr->GetOrderedRowSet(); TxnOrderedSet_t::iterator it = access_row_set.begin(); while (it != access_row_set.end()) { Access* ac = it->second; - if (ac->GetTxnRow()->GetTable() == table) { - if (ac->m_type == INS) + if (ac->GetTxnRow()->GetOrigTable() == table->GetOrigTable()) { + if (ac->m_type == INS) { RollbackInsert(ac); + // Row is local and was not inserted in the commit + if (ac->m_params.IsPrimarySentinel()) { + rows[ac->GetTxnRow()] = ac->GetTxnRow(); + } + } it = access_row_set.erase(it); // need to perform index clean-up! m_accessMgr->PubReleaseAccess(ac); } else { - it++; + (void)++it; } } + for (rowAddrMap_t::iterator itr = rows.begin(); itr != rows.end(); ++itr) { + itr->second->GetTable()->DestroyRow(itr->second); + } } - m_txnDdlAccess->Add(new_ddl_access); + txnTable->SetIsDropped(); + (void)m_txnDdlAccess->Add(new_ddl_access); return RC_OK; } RC TxnManager::TruncateTable(Table* table) { - RC res = RC_OK; - if (m_isLightSession) // really? - return res; + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); + if (m_isLightSession) { + return RC_OK; + } + + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + rowAddrMap_t rows; TxnOrderedSet_t& access_row_set = m_accessMgr->GetOrderedRowSet(); TxnOrderedSet_t::iterator it = access_row_set.begin(); while (it != access_row_set.end()) { Access* ac = it->second; - if (ac->GetTxnRow()->GetTable() == table) { - if (ac->m_type == INS) + if (ac->GetTxnRow()->GetOrigTable() == table->GetOrigTable()) { + if (ac->m_type == INS) { RollbackInsert(ac); + // Row is local and was not inserted in the commit + if (ac->m_params.IsPrimarySentinel()) { + rows[ac->GetTxnRow()] = ac->GetTxnRow(); + } + } it = access_row_set.erase(it); // need to perform index clean-up! m_accessMgr->PubReleaseAccess(ac); } else { - it++; + (void)++it; } } - + for (rowAddrMap_t::iterator itr = rows.begin(); itr != rows.end(); (void)++itr) { + itr->second->GetTable()->DestroyRow(itr->second); + } // clean all GC elements for the table, this call should actually release // all elements into an appropriate object pool for (uint16_t i = 0; i < table->GetNumIndexes(); i++) { MOT::Index* index = table->GetIndex(i); - GcManager::ClearIndexElements(index->GetIndexId(), false); + GcManager::ClearIndexElements(index->GetIndexId(), GC_OPERATION_TYPE::GC_OPER_TRUNCATE); } - TxnDDLAccess::DDLAccess* ddl_access = m_txnDdlAccess->GetByOid(table->GetTableExId()); - if (ddl_access == nullptr) { - MOTIndexArr* indexesArr = nullptr; - indexesArr = new (std::nothrow) MOTIndexArr(table); - if (indexesArr == nullptr) { - // print error, could not allocate memory - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Truncate Table", - "Failed to allocate memory for %u index objects", - (unsigned)MAX_NUM_INDEXES); - return RC_MEMORY_ALLOCATION_ERROR; - } + if (!txnTable->IsHasTruncate()) { + txnTable->SetHasTruncate(); // allocate DDL before work and fail immediately if required - ddl_access = new (std::nothrow) - TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_TRUNCATE_TABLE, (void*)indexesArr); + TxnDDLAccess::DDLAccess* ddl_access = new (std::nothrow) + TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_TRUNCATE_TABLE, (void*)txnTable); if (ddl_access == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Truncate Table", "Failed to allocate memory for DDL Access object"); - delete indexesArr; return RC_MEMORY_ALLOCATION_ERROR; } - if (!table->InitRowPool()) { - table->m_rowPool = indexesArr->GetRowPool(); - delete indexesArr; + if (!txnTable->InitRowPool(false) or !txnTable->InitTombStonePool(false)) { delete ddl_access; return RC_MEMORY_ALLOCATION_ERROR; } - for (uint16_t i = 0; i < table->GetNumIndexes(); i++) { - MOT::Index* index = table->GetIndex(i); - MOT::Index* index_copy = index->CloneEmpty(); - if (index_copy == nullptr) { - // print error, could not allocate memory for index - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Truncate Table", "Failed to clone empty index %s", index->GetName().c_str()); - for (uint16_t j = 0; j < indexesArr->GetNumIndexes(); j++) { - // cleanup of previous created indexes copy - MOT::Index* oldIndex = indexesArr->GetIndex(j); - uint16_t oldIx = indexesArr->GetIndexIx(j); - MOT::Index* newIndex = table->m_indexes[oldIx]; - table->m_indexes[oldIx] = oldIndex; - if (oldIx != 0) // is secondary - table->m_secondaryIndexes[oldIndex->GetName()] = oldIndex; - else // is primary - table->m_primaryIndex = oldIndex; - delete newIndex; - } - delete ddl_access; - delete indexesArr; - return RC_MEMORY_ALLOCATION_ERROR; - } - indexesArr->Add(i, index); - table->m_indexes[i] = index_copy; - if (i != 0) // is secondary - table->m_secondaryIndexes[index_copy->GetName()] = index_copy; - else // is primary - table->m_primaryIndex = index_copy; + res = TruncateIndexes(txnTable); + if (res != RC_OK) { + delete ddl_access; + return res; } - m_txnDdlAccess->Add(ddl_access); + + (void)m_txnDdlAccess->Add(ddl_access); + } else { + txnTable->SetHasTruncate(); } return res; } -RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool is_primary) +RC TxnManager::TruncateIndexes(TxnTable* txnTable) { + for (uint16_t i = 0; i < txnTable->GetNumIndexes(); i++) { + MOT::Index* index = txnTable->GetIndex(i); + if (!index->GetIsCommited()) { + GcManager::ClearIndexElements(index->GetIndexId()); + RC res = index->Truncate(false); + if (res != RC_OK) { + return res; + } + continue; + } + MOT::Index* index_copy = index->CloneEmpty(); + if (index_copy == nullptr) { + // print error, could not allocate memory for index + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Truncate Table", "Failed to clone empty index %s", index->GetName().c_str()); + return RC_MEMORY_ALLOCATION_ERROR; + } + index_copy->SetTable(txnTable); + txnTable->m_indexes[i] = index_copy; + if (i != 0) { // is secondary + txnTable->m_secondaryIndexes[index_copy->GetName()] = index_copy; + } else { // is primary + txnTable->m_primaryIndex = index_copy; + } + } + + return RC_OK; +} + +RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool isPrimary) +{ + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); size_t serializeRedoSize = table->SerializeItemSize(index); if (serializeRedoSize > RedoLogWriter::REDO_MAX_INDEX_SERIALIZE_SIZE) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, @@ -1157,6 +1699,14 @@ RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool is_primary) return RC_ERROR; } + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + txnTable->SetHasIndexChanges(isPrimary); + // allocate DDL before work and fail immediately if required TxnDDLAccess::DDLAccess* ddl_access = new (std::nothrow) TxnDDLAccess::DDLAccess(index->GetExtId(), DDL_ACCESS_CREATE_INDEX, (void*)index); @@ -1165,10 +1715,8 @@ RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool is_primary) return RC_MEMORY_ALLOCATION_ERROR; } - table->WrLock(); // for concurrent access - if (is_primary) { - if (!table->UpdatePrimaryIndex(index, this, m_threadId)) { - table->Unlock(); + if (isPrimary) { + if (!txnTable->UpdatePrimaryIndex(index, this, m_threadId)) { if (MOT_IS_OOM()) { // do not report error in "unique violation" scenario MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "Create Index", "Failed to add primary index %s", index->GetName().c_str()); @@ -1181,19 +1729,17 @@ RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool is_primary) // is should only be added on successful commit. Assuming that if // a client did a create index, all other clients are waiting on a lock // until the changes are either commited or aborted - if (table->GetNumIndexes() == MAX_NUM_INDEXES) { - table->Unlock(); + if (txnTable->GetNumIndexes() >= MAX_NUM_INDEXES) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Create Index", "Cannot create index in table %s: reached limit of %u indices per table", - table->GetLongTableName().c_str(), + txnTable->GetLongTableName().c_str(), (unsigned)MAX_NUM_INDEXES); delete ddl_access; return RC_TABLE_EXCEEDS_MAX_INDEXES; } - if (!table->AddSecondaryIndex(index->GetName(), index, this, m_threadId)) { - table->Unlock(); + if (!txnTable->AddSecondaryIndex(index->GetName(), index, this, m_threadId)) { if (MOT_IS_OOM()) { // do not report error in "unique violation" scenario MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "Create Index", "Failed to add secondary index %s", index->GetName().c_str()); @@ -1202,13 +1748,25 @@ RC TxnManager::CreateIndex(Table* table, MOT::Index* index, bool is_primary) return m_err; } } - table->Unlock(); - m_txnDdlAccess->Add(ddl_access); + (void)m_txnDdlAccess->Add(ddl_access); return RC_OK; } RC TxnManager::DropIndex(MOT::Index* index) { + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + Table* table = index->GetTable(); + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); + // allocate DDL before work and fail immediately if required TxnDDLAccess::DDLAccess* new_ddl_access = new (std::nothrow) TxnDDLAccess::DDLAccess(index->GetExtId(), DDL_ACCESS_DROP_INDEX, (void*)index); @@ -1217,8 +1775,7 @@ RC TxnManager::DropIndex(MOT::Index* index) return RC_MEMORY_ALLOCATION_ERROR; } - RC res = RC_OK; - Table* table = index->GetTable(); + txnTable->SetHasIndexChanges(true); if (!m_isLightSession && !index->IsPrimaryKey()) { TxnOrderedSet_t& access_row_set = m_accessMgr->GetOrderedRowSet(); @@ -1232,13 +1789,279 @@ RC TxnManager::DropIndex(MOT::Index* index) // need to perform index clean-up! m_accessMgr->PubReleaseAccess(ac); } else { - it++; + (void)++it; } } } - table->RemoveSecondaryIndexFromMetaData(index); - m_txnDdlAccess->Add(new_ddl_access); + txnTable->RemoveSecondaryIndexFromMetaData(index); + (void)m_txnDdlAccess->Add(new_ddl_access); return res; } + +RC TxnManager::AlterTableAddColumn(Table* table, Column* newColumn) +{ + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + TxnTable* txnTable = nullptr; + DDLAlterTableAddDropColumn* alterAdd = nullptr; + TxnDDLAccess::DDLAccess* new_ddl_access = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); + + do { + alterAdd = new (std::nothrow) DDLAlterTableAddDropColumn(); + if (alterAdd == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Add Column", "Failed to allocate alter structure"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + // allocate DDL before work and fail immediately if required + new_ddl_access = + new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_ADD_COLUMN, (void*)alterAdd); + if (new_ddl_access == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Add Column", "Failed to allocate DDL Access object"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + // clone column, in case we have drop this column later + alterAdd->m_column = Column::AllocColumn(newColumn->m_type); + if (alterAdd->m_column == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Add Column", "Failed to allocate column"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + res = alterAdd->m_column->Clone(newColumn); + if (res != RC_OK) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Add Column", "Failed to clone column"); + break; + } + txnTable->SetHasColumnChanges(); + res = txnTable->AlterAddColumn(this, newColumn); + } while (false); + if (res != RC_OK) { + if (new_ddl_access != nullptr) { + delete new_ddl_access; + } + if (alterAdd != nullptr) { + if (alterAdd->m_column != nullptr) { + delete alterAdd->m_column; + } + delete alterAdd; + } + } else { + alterAdd->m_table = txnTable; + (void)m_txnDdlAccess->Add(new_ddl_access); + } + + return res; +} + +RC TxnManager::AlterTableDropColumn(Table* table, Column* col) +{ + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + TxnDDLAccess::DDLAccess* new_ddl_access = nullptr; + DDLAlterTableAddDropColumn* alterDrop = nullptr; + TxnTable* txnTable = nullptr; + RC res = CreateTxnTable(table, txnTable); + if (res != RC_OK) { + return res; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); + + do { + alterDrop = new (std::nothrow) DDLAlterTableAddDropColumn(); + if (alterDrop == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Drop Column", "Failed to allocate alter structure"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + // allocate DDL before work and fail immediately if required + new_ddl_access = + new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_DROP_COLUMN, (void*)alterDrop); + if (new_ddl_access == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Drop Column", "Failed to allocate DDL Access object"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + // clone column, in case we have drop this column later + alterDrop->m_column = Column::AllocColumn(col->m_type); + if (alterDrop->m_column == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Drop Column", "Failed to allocate column"); + res = RC_MEMORY_ALLOCATION_ERROR; + break; + } + res = alterDrop->m_column->Clone(col); + if (res != RC_OK) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Drop Column", "Failed to clone column"); + break; + } + txnTable->SetHasColumnChanges(); + res = txnTable->AlterDropColumn(this, col); + } while (false); + + if (res != RC_OK) { + if (new_ddl_access != nullptr) { + delete new_ddl_access; + } + if (alterDrop != nullptr) { + if (alterDrop->m_column != nullptr) { + delete alterDrop->m_column; + } + delete alterDrop; + } + } else { + alterDrop->m_table = txnTable; + (void)m_txnDdlAccess->Add(new_ddl_access); + } + + return res; +} + +RC TxnManager::AlterTableRenameColumn(Table* table, Column* col, char* newname) +{ + if (!m_txnDdlAccess->HasEnoughSpace()) { + return RC_TXN_EXCEEDS_MAX_DDLS; + } + + TxnTable* txnTable = nullptr; + RC rc = CreateTxnTable(table, txnTable); + if (rc != RC_OK) { + return rc; + } + + SetTxnOper(SubTxnOperType::SUB_TXN_DDL); + + // clean all GC elements for the table, this call should actually release + // all elements into an appropriate object pool + for (uint16_t i = 0; i < table->GetNumIndexes(); i++) { + MOT::Index* index = table->GetIndex(i); + GcManager::ClearIndexElements(index->GetIndexId(), GC_OPERATION_TYPE::GC_ALTER_TABLE); + } + + DDLAlterTableRenameColumn* alterRename = new (std::nothrow) DDLAlterTableRenameColumn(); + if (alterRename == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Rename Column", "Failed to allocate alter structure"); + return RC_MEMORY_ALLOCATION_ERROR; + } + // allocate DDL before work and fail immediately if required + TxnDDLAccess::DDLAccess* new_ddl_access = + new (std::nothrow) TxnDDLAccess::DDLAccess(table->GetTableExId(), DDL_ACCESS_RENAME_COLUMN, (void*)alterRename); + if (new_ddl_access == nullptr) { + delete alterRename; + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Rename Column", "Failed to allocate DDL Access object"); + return RC_MEMORY_ALLOCATION_ERROR; + } + + txnTable->SetHasColumnRename(); + rc = txnTable->AlterRenameColumn(this, col, newname, alterRename); + if (rc != RC_OK) { + delete new_ddl_access; + delete alterRename; + } else { + alterRename->m_table = txnTable; + (void)m_txnDdlAccess->Add(new_ddl_access); + } + + return rc; +} +TxnIxColUpdate::TxnIxColUpdate(Table* tab, TxnManager* txn, uint8_t* modBitmap, UpdateIndexColumnType hasIxColUpd) + : m_txn(txn), + m_tab(tab), + m_modBitmap(modBitmap), + m_arrLen(tab->GetNumIndexes()), + m_cols(tab->GetFieldCount() - 1), + m_hasIxColUpd(hasIxColUpd) +{} + +TxnIxColUpdate::~TxnIxColUpdate() +{ + if (m_hasIxColUpd) { + for (uint16_t j = 0; j < m_arrLen; j++) { + if (m_oldKeys[j] != nullptr) { + m_ix[j]->DestroyKey(m_oldKeys[j]); + } + if (m_newKeys[j] != nullptr) { + m_ix[j]->DestroyKey(m_newKeys[j]); + } + } + } + + m_txn = nullptr; + m_tab = nullptr; + m_modBitmap = nullptr; +} + +RC TxnIxColUpdate::InitAndBuildOldKeys(Row* row) +{ + RC rc = RC_OK; + + for (uint64_t i = 0; i < m_cols; i++) { + if (BITMAP_GET(m_modBitmap, i)) { + if (m_tab->GetField((uint16_t)(i + 1))->IsUsedByIndex()) { + for (uint16_t j = 0; j < m_arrLen; j++) { + if (m_ix[j] == nullptr) { + MOT::Index* ix = m_tab->GetIndex(j); + if (ix->IsFieldPresent((uint16_t)(i + 1))) { + m_ix[j] = ix; + } + } + } + } + } + } + for (uint16_t j = 0; j < m_arrLen; j++) { + if (m_ix[j] != nullptr) { + m_oldKeys[j] = m_ix[j]->CreateNewKey(); + if (m_oldKeys[j] == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Insert Row", + "Failed to create key for secondary index %s", + m_ix[j]->GetName().c_str()); + rc = MOT::RC::RC_MEMORY_ALLOCATION_ERROR; + break; + } + m_ix[j]->BuildKey(m_tab, row, m_oldKeys[j]); + } + } + return rc; +} + +RC TxnIxColUpdate::FilterColumnUpdate(Row* row) +{ + RC rc = RC_OK; + for (uint16_t j = 0; j < m_arrLen; j++) { + if (m_ix[j] != nullptr) { + m_newKeys[j] = m_ix[j]->CreateNewKey(); + if (m_newKeys[j] == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Insert Row", + "Failed to create key for secondary index %s", + m_ix[j]->GetName().c_str()); + rc = MOT::RC::RC_MEMORY_ALLOCATION_ERROR; + break; + } + m_ix[j]->BuildKey(m_tab, row, m_newKeys[j]); + // check if key has changed, can happen in case we update to a same value + if (memcmp(m_newKeys[j]->GetKeyBuf(), m_oldKeys[j]->GetKeyBuf(), m_ix[j]->GetKeySizeNoSuffix()) == 0) { + m_ix[j]->DestroyKey(m_oldKeys[j]); + m_ix[j]->DestroyKey(m_newKeys[j]); + m_ix[j] = nullptr; + m_oldKeys[j] = nullptr; + m_newKeys[j] = nullptr; + } + } + } + return rc; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn.h b/src/gausskernel/storage/mot/core/system/transaction/txn.h index 408326564..27e223d88 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn.h +++ b/src/gausskernel/storage/mot/core/system/transaction/txn.h @@ -40,8 +40,9 @@ #include "mm_gc_manager.h" #include "bitmapset.h" #include "txn_ddl_access.h" +#include "sub_txn_mgr.h" #include "mm_session_api.h" -#include "commit_sequence_number.h" +#include "mm_buffer_api.h" namespace MOT { class MOTContext; @@ -56,6 +57,9 @@ class InsItem; class LoggerTask; class Key; class Index; +class TxnTable; +class DummyIndex; +class TxnIxColUpdate; class TransactionIdManager { public: @@ -65,11 +69,11 @@ public: ~TransactionIdManager() {} - /** @brief Used to enforce the last transaction id value (used after recovery). */ + /** @brief Used to enforce the last transaction id value at the end of the recovery. */ void SetId(uint64_t value) { if (m_id.load() < value) { - m_id = value; + m_id.store(value); } } @@ -79,7 +83,7 @@ public: uint64_t current = 0; uint64_t next = 0; do { - current = m_id; + current = m_id.load(); next = current + 1; } while (!m_id.compare_exchange_strong(current, next, std::memory_order_acq_rel)); return next; @@ -88,7 +92,7 @@ public: /** @brief Get the current id. This method does not change the value of the current id. */ inline uint64_t GetCurrentId() const { - return m_id; + return m_id.load(); } private: @@ -113,15 +117,16 @@ public: * @brief Constructor. * @param session_context The owning session's context. */ - TxnManager(SessionContext* sessionContext); + explicit TxnManager(SessionContext* sessionContext, bool global = false); /** * @brief Initializes the object * @param threadId The logical identifier of the thread executing this transaction. * @param connectionId The logical identifier of the connection executing this transaction. + * @param isRecoveryTxn Type of txnManager. * @return Boolean value denoting success or failure. */ - bool Init(uint64_t threadId, uint64_t connectionId, bool isLightTxn = false); + bool Init(uint64_t threadId, uint64_t connectionId, bool isRecoveryTxn, bool isLightTxn = false); /** * @brief Override placement-new operator. @@ -145,11 +150,24 @@ public: return m_threadId; } // Attention: this may be invalid in thread-pooled envelope! + inline void SetThdId(uint64_t tid) + { + m_threadId = tid; + if (m_gcSession != nullptr) { + m_gcSession->SetThreadId(tid); + } + } + inline uint64_t GetConnectionId() const { return m_connectionId; } + inline void SetConnectionId(uint64_t id) + { + m_connectionId = id; + } + inline uint64_t GetCommitSequenceNumber() const { return m_csn; @@ -190,13 +208,96 @@ public: return m_replayLsn; } + bool IsReadOnlyTxn() const; + + bool hasDDL() const; + void RemoveTableFromStat(Table* t); /** * @brief Start transaction, set transaction id and isolation level. + * @return void. + */ + void StartTransaction(uint64_t transactionId, int isolationLevel); + + /** + * @brief Start sub transaction, set sub-transaction Id and isolation level. + * @return void. + */ + void StartSubTransaction(uint64_t subTransactionId, int isolationLevel); + + /** + * @brief Commit sub transaction. + * @return void. + */ + void CommitSubTransaction(uint64_t subTransactionId); + + /** + * @brief Rollback sub transaction. * @return Result code denoting success or failure. */ - RC StartTransaction(uint64_t transactionId, int isolationLevel); + RC RollbackSubTransaction(uint64_t subTransactionId); + + void SetTxnOper(SubTxnOperType subOper) + { + m_subTxnManager.SetSubTxnOper(subOper); + } + + bool IsSubTxnStarted() const + { + return m_subTxnManager.IsSubTxnStarted(); + } + + bool IsTxnAborted() const + { + return m_isTxnAborted; + } + + void SetTxnAborted() + { + m_isTxnAborted = true; + } + + void SetHasCommitedSubTxnDDL() + { + m_hasCommitedSubTxnDDL = true; + } + + bool HasCommitedSubTxnDDL() const + { + return m_hasCommitedSubTxnDDL; + } + + /** + * @brief Checks if the transaction has any modifications in disk engine as well. + * Attention: This flag is set only in validate commit phase, so this interface should not be used before that. + * Used only by RedoLogWriter to mark the transaction as a cross engine transaction. Recovery needs to know if + * the transaction is MOT only or Cross Engine transaction. + */ + bool IsCrossEngineTxn() const + { + return m_isCrossEngineTxn; + } + + /** + * @brief Sets the flag to indicate that the transaction has modifications in disk engine. This is set only + * during the validate commit phase (before writing redo) to indicate that the transaction has already inserted + * XLOG records for the modifications done in the disk engine. + */ + void MarkAsCrossEngineTxn() + { + m_isCrossEngineTxn = true; + } + + bool IsRecoveryTxn() const + { + return m_isRecoveryTxn; + } + + bool IsUpdateIndexColumn() const + { + return m_hasIxColUpd; + } /** * @brief Performs pre-commit validation (OCC validation). @@ -275,25 +376,17 @@ public: /** * @brief Inserts a row and updates all affected indices. * @param row The row to insert. - * @param insItem The affected index keys. - * @param insItemSize The number of affected index keys. + * @param isUpdateColumn Indicate whether the insert source is update column * @return Return code denoting the execution result. */ - RC InsertRow(Row* row); + RC InsertRow(Row* row, Key* updateColumnKey = nullptr); + + RC InsertRecoveredRow(Row* row); InsItem* GetNextInsertItem(Index* index = nullptr); Key* GetTxnKey(Index* index); - void DestroyTxnKey(Key* key) const - { - MemSessionFree(key); - } - - /** - * @brief Delete a row - * @return Return code denoting the execution result. - */ - RC RowDel(); + void DestroyTxnKey(Key* key) const; /** * @brief Searches for a row in the local cache by a primary key. @@ -305,7 +398,7 @@ public: * @param currentKey The primary key by which to search the row. * @return The row or null pointer if none was found. */ - Row* RowLookupByKey(Table* const& table, const AccessType type, MOT::Key* const currentKey); + Row* RowLookupByKey(Table* const& table, const AccessType type, MOT::Key* const currentKey, RC& rc); /** * @brief Searches for a row in the local cache by a row. @@ -319,6 +412,8 @@ public: */ Row* RowLookup(const AccessType type, Sentinel* const& originalSentinel, RC& rc); + bool IsRowExist(Sentinel* const& sentinel, RC& rc); + /** * @brief Searches for a row in the local cache by a row. * @detail Rows may be updated concurrently, so the cache layer needs to be @@ -331,18 +426,6 @@ public: */ RC AccessLookup(const AccessType type, Sentinel* const& originalSentinel, Row*& localRow); - /** - * @brief Searches in the cache for the row following a secondary index item. - * @param table The table in which the row is to be searched. - * @param type The purpose for retrieving the row. - * @param[in,out] curr_item The secondary index item. The secondary index - * item is advanced to the next item. - * @return The cached row or the original row stored in the secondary index - * item if none was found in the cache (in which case the original row is - * stored in the cache for subsequent searches). - */ - inline Row* RowLookupSecondaryNextSame(Table* table, AccessType type, void*& curr_item); - RC DeleteLastRow(); /** @@ -352,17 +435,16 @@ public: */ RC UpdateLastRowState(AccessType state); - void CommitSecondaryItems(); + void UndoLocalDMLChanges(); - Row* RemoveKeyFromIndex(Row* row, Sentinel* sentinel); + void RollbackInsert(Access* ac); - void UndoInserts(); - - RC RollbackInsert(Access* ac); void RollbackSecondaryIndexInsert(Index* index); + void RollbackDDLs(); - RC OverwriteRow(Row* updatedRow, BitmapSet& modifiedColumns); + RC OverwriteRow(Row* updatedRow, const BitmapSet& modifiedColumns); + RC UpdateRow(const BitmapSet& modifiedColumns, TxnIxColUpdate* colUpd); /** * @brief Updates the value in a single field in a row. @@ -370,16 +452,23 @@ public: * @param attr_id The column identifier. * @param attr_value The new field value. */ - void UpdateRow(Row* row, const int attr_id, double attr_value); - void UpdateRow(Row* row, const int attr_id, uint64_t attr_value); + RC UpdateRow(Row* row, const int attr_id, double attr_value); + RC UpdateRow(Row* row, const int attr_id, uint64_t attr_value); + + Row* GetLastAccessedDraft(); /** * @brief Retrieves the latest epoch seen by this transaction. * @return The latest epoch. */ - inline uint64_t GetLatestEpoch() const + inline uint64_t GetVisibleCSN() const { - return m_latestEpoch; + return m_visibleCSN; + } + + inline void SetVisibleCSN(uint64_t csn) + { + m_visibleCSN = csn; } /** @@ -400,7 +489,7 @@ public: * @brief Retrieves the transaction isolation level. * @return The transaction isolation level. */ - inline int GetTxnIsoLevel() const + inline ISOLATION_LEVEL GetIsolationLevel() const { return m_isolationLevel; } @@ -408,7 +497,13 @@ public: /** * @brief Sets the transaction isolation level. */ - void SetTxnIsoLevel(int envelopeIsoLevel); + inline void SetIsolationLevel(int envelopeIsoLevel) + { + m_isolationLevel = static_cast(envelopeIsoLevel); + if (m_isolationLevel == SERIALIZABLE) { + m_isolationLevel = REPEATABLE_READ; + } + } inline void IncStmtCount() { @@ -430,22 +525,33 @@ public: return m_surrogateGen.GetCurrentCount(); } - inline void GcSessionStart() + RC GcSessionStart(uint64_t csn = 0) { - m_gcSession->GcStartTxn(); + return m_gcSession->GcStartTxn(csn); } - void GcSessionRecordRcu( - uint32_t index_id, void* object_ptr, void* object_pool, DestroyValueCbFunc cb, uint32_t obj_size); + void GcSessionRecordRcu(GC_QUEUE_TYPE m_type, uint32_t index_id, void* object_ptr, void* object_pool, + DestroyValueCbFunc cb, uint32_t obj_size, uint64_t csn = 0); inline void GcSessionEnd() { m_gcSession->GcEndTxn(); } + inline void GcSessionEndRecovery() + { + m_gcSession->GcRecoveryEndTxn(); + } + + inline void GcEndStatement() + { + MOT_ASSERT(IsReadOnlyTxn()); + m_gcSession->GcEndStatement(); + } + inline void GcAddSession() { - if (m_isLightSession == true) { + if (m_isLightSession) { return; } m_gcSession->AddToGcList(); @@ -453,13 +559,13 @@ public: inline void GcRemoveSession() { - if (m_isLightSession == true) { + if (m_isLightSession) { return; } m_gcSession->GcCleanAll(); m_gcSession->RemoveFromGcList(m_gcSession); m_gcSession->~GcManager(); - MemSessionFree(m_gcSession); + MemFree(m_gcSession, m_global); m_gcSession = nullptr; } void RedoWriteAction(bool isCommit); @@ -469,6 +575,30 @@ public: return m_gcSession; } + inline bool GetSnapshotStatus() const + { + return m_isSnapshotTaken; + } + + inline void SetSnapshotStatus(bool status) + { + m_isSnapshotTaken = status; + } + + inline void FinishStatement() + { + if (GetIsolationLevel() == READ_COMMITED) { + SetVisibleCSN(0); + m_isSnapshotTaken = false; + // For read-only transactions allow GC to reclaim data between statements + if (IsReadOnlyTxn()) { + GcEndStatement(); + } + } + IncStmtCount(); + } + + RC SetSnapshot(); /** * @brief Sets or clears the validate-no-wait flag in OccTransactionManager. * @detail Determines whether to call Access::lock() or @@ -482,6 +612,53 @@ public: bool IsUpdatedInCurrStmt(); + Key* GetLocalKey() const + { + return m_key; + } + + void SetReservedChunks(size_t chunks) + { + m_reservedChunks += chunks; + } + + void DecReservedChunks(size_t chunks) + { + if (m_reservedChunks > chunks) { + (void)MemBufferUnreserveGlobal(chunks); + m_reservedChunks -= chunks; + } else { + (void)MemBufferUnreserveGlobal(0); + m_reservedChunks = 0; + } + } + + void ClearReservedChunks() + { + if (m_reservedChunks > 0) { + (void)MemBufferUnreserveGlobal(0); + m_reservedChunks = 0; + } + } + + inline void SetCheckpointCommitEnded(bool value) + { + m_checkpointCommitEnded = value; + } + + Table* GetTableByExternalId(uint64_t id); + Index* GetIndexByExternalId(uint64_t tableId, uint64_t indexId); + RC CreateTxnTable(Table* table, TxnTable*& txnTable); + Table* GetTxnTable(uint64_t tableId); + RC CreateTable(Table* table); + RC DropTable(Table* table); + RC CreateIndex(Table* table, Index* index, bool isPrimary); + RC DropIndex(Index* index); + RC TruncateTable(Table* table); + RC AlterTableAddColumn(Table* table, Column* newColumn); + RC AlterTableDropColumn(Table* table, Column* col); + RC AlterTableRenameColumn(Table* table, Column* col, char* newname); + private: static constexpr uint32_t SESSION_ID_BITS = 32; @@ -489,7 +666,7 @@ private: * @brief Apply all transactional DDL changes. DDL changes are not handled * by occ. */ - void CleanDDLChanges(); + void ApplyDDLChanges(); /** * @brief Reclaims all resources associated with the transaction and @@ -509,6 +686,10 @@ private: void RollbackInternal(bool isPrepared); + RC TruncateIndexes(TxnTable* txnTable); + + void ReclaimAccessSecondaryDelKey(Access* ac); + // Disable class level new operator /** @cond EXCLUDE_DOC */ void* operator new(std::size_t size) = delete; @@ -526,23 +707,13 @@ private: friend class OccTransactionManager; friend class SessionContext; -public: - Table* GetTableByExternalId(uint64_t id); - Index* GetIndexByExternalId(uint64_t table_id, uint64_t index_id); - RC CreateTable(Table* table); - RC DropTable(Table* table); - RC CreateIndex(Table* table, Index* index, bool is_primary); - RC DropIndex(Index* index); - RC TruncateTable(Table* table); - -private: /** @var The latest epoch seen. */ - uint64_t m_latestEpoch; + uint64_t m_visibleCSN = 0; - /** @vat The logical identifier of the thread executing this transaction. */ + /** @var The logical identifier of the thread executing this transaction. */ uint64_t m_threadId; - /** @vat The logical identifier of the connection executing this transaction. */ + /** @var The logical identifier of the connection executing this transaction. */ uint64_t m_connectionId; /** @var Owning session context. */ @@ -562,6 +733,9 @@ private: /** @var Checkpoint not available capture during being transaction. */ volatile bool m_checkpointNABit; + /** @var Indicates if the checkpoint EndCommit was called. */ + volatile bool m_checkpointCommitEnded; + /** @var CSN taken at the commit stage. */ uint64_t m_csn; @@ -576,26 +750,47 @@ private: bool m_flushDone; - /** @var internal_transaction_id Generated by txn_manager. */ - /** It is a concatenation of session_id and a counter */ + /** @var Unique Internal transaction id (Used only for redo and recovery). */ uint64_t m_internalTransactionId; uint32_t m_internalStmtCount; - /** @brief Transaction state. - * this state only reflects the envelope states to avoid invalid transitions + /** + * @brief Transaction state. + * This state only reflects the envelope states to avoid invalid transitions. * Transaction state machine is managed by the envelope! */ TxnState m_state; - int m_isolationLevel; + ISOLATION_LEVEL m_isolationLevel; + + bool m_isSnapshotTaken = false; + + bool m_isTxnAborted; + + bool m_hasCommitedSubTxnDDL; + + /** + * @var Indicates whether this is a cross engine (write) transaction which has modifications in disk engine also. + * Attention: This is set only during XACT_EVENT_COMMIT callback so that this information is written in the redo + * and used in the recovery. So this should not be used/queried before XACT_EVENT_COMMIT phase. + * Used only by RedoLogWriter to mark the transaction as a cross engine transaction. Recovery needs to know if + * the transaction is MOT only or Cross Engine transaction. + */ + bool m_isCrossEngineTxn; + + bool m_isRecoveryTxn = false; + + bool m_hasIxColUpd = false; public: /** @var Transaction cache (OCC optimization). */ - MemSessionPtr m_accessMgr; + TxnAccess* m_accessMgr; TxnDDLAccess* m_txnDdlAccess; + SubTxnMgr m_subTxnManager; + MOT::Key* m_key; bool m_isLightSession; @@ -610,6 +805,32 @@ public: /** @var holds query states from MOTAdaptor */ std::unordered_map m_queryState; + + size_t m_reservedChunks; + + // use global or local memory allocators + bool m_global; + + MOT::DummyIndex* m_dummyIndex; +}; + +class TxnIxColUpdate { +public: + MOT::Index* m_ix[MAX_NUM_INDEXES] = {nullptr}; + MOT::Key* m_oldKeys[MAX_NUM_INDEXES] = {nullptr}; + MOT::Key* m_newKeys[MAX_NUM_INDEXES] = {nullptr}; + TxnManager* m_txn = nullptr; + Table* m_tab = nullptr; + uint8_t* m_modBitmap; + uint16_t m_arrLen = 0; + uint64_t m_cols = 0; + UpdateIndexColumnType m_hasIxColUpd = UpdateIndexColumnType::UPDATE_COLUMN_NONE; + + TxnIxColUpdate(Table* tab, TxnManager* txn, uint8_t* modBitmap, UpdateIndexColumnType hasIxColUpd); + ~TxnIxColUpdate(); + + RC InitAndBuildOldKeys(Row* row); + RC FilterColumnUpdate(Row* row); }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_access.cpp b/src/gausskernel/storage/mot/core/system/transaction/txn_access.cpp index aba738ed8..c4afd79ff 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_access.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_access.cpp @@ -23,20 +23,31 @@ */ #include - +#include +#include #include "table.h" #include "row.h" #include "txn.h" #include "txn_access.h" #include "txn_insert_action.h" +#include "mot_engine.h" namespace MOT { IMPLEMENT_CLASS_LOGGER(TxnInsertAction, TxMan); IMPLEMENT_CLASS_LOGGER(TxnAccess, TxMan); +const char* const TxnAccess::enTxnStates[] = {stringify(INV), + stringify(RD), + stringify(RD_FOR_UPDATE), + stringify(WR), + stringify(DEL), + stringify(INS), + stringify(SCAN), + stringify(TEST)}; + TxnAccess::TxnAccess() - : m_accessesSetBuff(nullptr), - m_rowCnt(0), + : m_rowCnt(0), + m_accessesSetBuff(nullptr), m_txnManager(nullptr), m_insertManager(nullptr), m_lastAcc(nullptr), @@ -57,26 +68,41 @@ TxnAccess::~TxnAccess() if (m_insertManager) { delete m_insertManager; } - + if (m_sentinelObjectPool) { + delete m_sentinelObjectPool; + } // fall through case CreateRowZero: if (m_rowZero) { - m_dummyTable.DestroyMaxRow(m_rowZero); + MemFree(m_rowZero, m_txnManager->m_global); } - // fall through case InitDummyTab: // fall through case AllocBuf: if (m_accessesSetBuff) { - MemSessionFree(m_accessesSetBuff); + MemFree(m_accessesSetBuff, m_txnManager->m_global); } - // fall through case Startup: default: break; } + + if (m_accessPool) { + ObjAllocInterface::FreeObjPool(&m_accessPool); + m_accessPool = nullptr; + } + + m_tableStat.clear(); + + m_txnManager = nullptr; + m_insertManager = nullptr; + m_accessesSetBuff = nullptr; + m_sentinelObjectPool = nullptr; + m_rowsSet = nullptr; + m_rowZero = nullptr; + m_lastAcc = nullptr; } void TxnAccess::ClearAccessSet() @@ -85,59 +111,59 @@ void TxnAccess::ClearAccessSet() Access* ac = GetAccessPtr(i); if (ac != nullptr) { DestroyAccess(ac); - delete ac; + ReleaseAccessToPool(ac); } else { break; } } m_allocatedAc = 0; - MemSessionFree(m_accessesSetBuff); + MemFree(m_accessesSetBuff, m_txnManager->m_global); m_accessesSetBuff = nullptr; } bool TxnAccess::Init(TxnManager* manager) { m_initPhase = Startup; + SessionId sessionId = + manager->GetSessionContext() ? manager->GetSessionContext()->GetSessionId() : INVALID_SESSION_ID; // allocate buffer m_txnManager = manager; uint32_t alloc_size = sizeof(Access*) * m_accessSetSize; - void* ptr = MemSessionAlloc(alloc_size); + void* ptr = MemAlloc(alloc_size, m_txnManager->m_global); if (ptr == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Transaction Initialization", "Failed to allocate %u bytes aligned to 64 bytes for TxnAccess in session %u", alloc_size, - manager->GetSessionContext()->GetSessionId()); + sessionId); return false; } m_initPhase = AllocBuf; - m_accessesSetBuff = reinterpret_cast(ptr); + m_accessesSetBuff = static_cast(ptr); for (uint64_t idx = 0; idx < m_accessSetSize; idx++) { m_accessesSetBuff[idx] = nullptr; } // initialize dummy table - if (!m_dummyTable.Init()) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Transaction Initialization", - "Failed to initialize dummy table for session %u", - manager->GetSessionContext()->GetSessionId()); + if (!m_dummyTable.Init(m_txnManager->m_isRecoveryTxn, m_txnManager->m_global)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Transaction Initialization", "Failed to initialize dummy table for session %u", sessionId); return false; } m_initPhase = InitDummyTab; // create row zero - m_rowZero = m_dummyTable.CreateMaxRow(); - if (!m_rowZero) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Transaction Initialization", - "Failed to allocate row zero for session %u", - manager->GetSessionContext()->GetSessionId()); + ptr = MemAlloc(sizeof(Row) + MAX_TUPLE_SIZE, m_txnManager->m_global); + if (!ptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Transaction Initialization", "Failed to allocate row zero for session %u", sessionId); return false; } + m_rowZero = new (ptr) Row(nullptr); + m_rowZero->m_rowType = RowType::ROW_ZERO; m_initPhase = CreateRowZero; // create rows set @@ -149,21 +175,34 @@ bool TxnAccess::Init(TxnManager* manager) manager->GetSessionContext()->GetSessionId()); return false; } - m_initPhase = CreateRowSet; - - m_insertManager = new (std::nothrow) TxnInsertAction(); - if (m_insertManager == nullptr) { + m_sentinelObjectPool = new (std::nothrow) S_SentinelNodePool(); + if (m_sentinelObjectPool == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Transaction Initialization", - "Failed to allocate Insert set for session %u", + "Failed to allocate sentinel node set for session %u", manager->GetSessionContext()->GetSessionId()); return false; } + m_initPhase = CreateInsertSet; + m_insertManager = new (std::nothrow) TxnInsertAction(); + if (m_insertManager == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Transaction Initialization", "Failed to allocate Insert set for session %u", sessionId); + return false; + } + if (!m_insertManager->Init(manager)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Transaction Initialization", "Failed to INIT Insert set for session %u", sessionId); + return false; + } + m_initPhase = CreateAccessPool; + m_accessPool = ObjAllocInterface::GetObjPool(sizeof(Access), !manager->m_global); + if (!m_accessPool) { MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Transaction Initialization", - "Failed to INIT Insert set for session %u", + "Initialize ObjectPool", + "Failed to allocate Access pool for session %d", manager->GetSessionContext()->GetSessionId()); return false; } @@ -184,19 +223,62 @@ void TxnAccess::ClearSet() } m_rowCnt = 0; } + + m_sentinelObjectPool->clear(); m_insertManager->ClearSet(); } void TxnAccess::DestroyAccess(Access* access) { - Row* row = access->m_localRow; - if (row != nullptr) { - m_dummyTable.DestroyRow(row, access); + if (access->m_localRow != nullptr) { + if (access->m_params.IsPrimarySentinel()) { + Row* row = access->m_localRow; + Table* table = row->GetTable(); + table->DestroyRow(row); + } access->m_localRow = nullptr; } - if (access->m_modifiedColumns.IsInitialized()) { - m_dummyTable.DestroyBitMapBuffer(access->m_modifiedColumns.GetData(), access->m_modifiedColumns.GetSize()); - access->m_modifiedColumns.Reset(); + + if (access->m_params.IsPrimarySentinel()) { + if (access->m_modifiedColumns.IsInitialized()) { + m_dummyTable.DestroyBitMapBuffer(access->m_modifiedColumns.GetData(), access->m_modifiedColumns.GetSize()); + access->m_modifiedColumns.Reset(); + } + } else { + if (access->m_secondaryDelKey != nullptr) { + access->m_origSentinel->GetIndex()->DestroyKey(access->m_secondaryDelKey); + access->m_secondaryDelKey = nullptr; + } + } + + if (access->m_type == INS and access->m_secondaryUniqueNode) { + auto object = m_sentinelObjectPool->find(access->m_origSentinel->GetIndex()); + if (object != m_sentinelObjectPool->end()) { + PrimarySentinelNode* node = object->second; + (void)m_sentinelObjectPool->erase(object); + access->m_origSentinel->GetIndex()->SentinelNodeRelease(node); + } + access->m_secondaryUniqueNode = nullptr; + } + access->m_origSentinel = nullptr; + access->m_type = AccessType::INV; +} + +void TxnAccess::ReleaseSubTxnAccesses(uint32_t accessId) +{ + Access* ac = nullptr; + uint32_t endIdx = m_rowCnt; + if (m_accessesSetBuff[accessId] == nullptr) { + return; + } + MOT_LOG_DEBUG("Rollback Access range %d to %d ", accessId, endIdx); + for (uint32_t startIdx = accessId; startIdx < endIdx; startIdx++) { + ac = m_accessesSetBuff[startIdx]; + auto it = m_rowsSet->find(ac->m_origSentinel); + MOT_ASSERT(it != m_rowsSet->end()); + (void)m_rowsSet->erase(it); + DestroyAccess(ac); + m_rowCnt--; } } @@ -204,14 +286,35 @@ bool TxnAccess::ReallocAccessSet() { bool rc = true; uint64_t new_array_size = m_accessSetSize * ACCESS_SET_EXTEND_FACTOR; + if (new_array_size >= UINT32_MAX) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Transaction Execution", + "Txn crossed limit of 32bit elements %lu in session %u (while reallocating)", + new_array_size, + m_txnManager->GetSessionContext()->GetSessionId()); + SetLastError(MOT_ERROR_OOM, MOT_SEVERITY_ERROR); + return false; + } + + // Reserve memory for rollback + rc = m_txnManager->GetGcSession()->ReserveGCRollbackMemory(static_cast(new_array_size)); + if (!rc) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Transaction Execution", + "GC Failed to reserve %d elements in session %u (while reallocating)", + m_accessSetSize, + m_txnManager->GetSessionContext()->GetSessionId()); + SetLastError(MOT_ERROR_OOM, MOT_SEVERITY_ERROR); + return rc; + } MOT_LOG_DEBUG("Increasing Access Size! from %d to %lu", m_accessSetSize, new_array_size); - uint32_t alloc_size = sizeof(Access*) * new_array_size; - void* ptr = MemSessionAlloc(alloc_size); + uint64_t alloc_size = sizeof(Access*) * new_array_size; + void* ptr = MemAlloc(alloc_size, m_txnManager->m_global); if (ptr == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Transaction Execution", - "Failed to allocate %u bytes aligned to 64 bytes for TxnAccess in session %u (while reallocating)", + "Failed to allocate %lu bytes aligned to 64 bytes for TxnAccess in session %u (while reallocating)", alloc_size, m_txnManager->GetSessionContext()->GetSessionId()); SetLastError(MOT_ERROR_OOM, MOT_SEVERITY_ERROR); @@ -221,7 +324,7 @@ bool TxnAccess::ReallocAccessSet() securec_check(erc, "\0", "\0"); erc = memcpy_s(ptr, sizeof(Access*) * new_array_size, m_accessesSetBuff, sizeof(Access*) * m_accessSetSize); securec_check(erc, "\0", "\0"); - MemSessionFree(m_accessesSetBuff); + MemFree(m_accessesSetBuff, m_txnManager->m_global); SetAccessesSet(reinterpret_cast(ptr)); m_accessSetSize = new_array_size; } @@ -235,17 +338,20 @@ void TxnAccess::ShrinkAccessSet() // Clear access set for (unsigned int i = 0; i < m_allocatedAc; i++) { DestroyAccess(m_accessesSetBuff[i]); - delete m_accessesSetBuff[i]; + ReleaseAccessToPool(m_accessesSetBuff[i]); } + m_allocatedAc = 0; + m_rowCnt = 0; if (new_array_size < m_accessSetSize) { m_dummyTable.ClearRowCache(); + m_accessPool->ClearFreeCache(); } MOT_LOG_DEBUG("Shrinking Access Size! from %d to %lu", m_accessSetSize, new_array_size); uint32_t alloc_size = sizeof(Access*) * new_array_size; - void* ptr = MemSessionAlloc(alloc_size); + void* ptr = MemAlloc(alloc_size, m_txnManager->m_global); if (ptr == nullptr) { MOT_LOG_ERROR("Failed to allocate %u bytes aligned to 64 bytes for TxnAccess in session %u (while shrinking)", alloc_size, @@ -255,21 +361,63 @@ void TxnAccess::ShrinkAccessSet() erc = memset_s(ptr, alloc_size, 0, sizeof(Access*) * new_array_size); securec_check(erc, "\0", "\0"); - MemSessionFree(m_accessesSetBuff); + MemFree(m_accessesSetBuff, m_txnManager->m_global); SetAccessesSet(reinterpret_cast(ptr)); m_accessSetSize = new_array_size; - m_allocatedAc = 0; - m_rowCnt = 0; } -Access* TxnAccess::GetNewRowAccess(const Row* row, AccessType type, RC& rc) +Access* TxnAccess::GetNewInsertAccess(Sentinel* const& sentinel, Row* row, RC& rc, bool isUpgrade) { rc = RC_OK; - TransactionId last_tid; - Access* ac = GetAccessPtr(m_rowCnt); + Row* oldVersion = nullptr; + PrimarySentinelNode* node = nullptr; + uint64_t primary_csn = 0; + uint64_t csn = m_txnManager->GetVisibleCSN(); + if (isUpgrade) { + switch (sentinel->GetIndexOrder()) { + case IndexOrder::INDEX_ORDER_PRIMARY: + // Guard the sentinel from reclamation when validating the row + static_cast(sentinel)->GetGcInfo().Lock(); + oldVersion = sentinel->GetData(); + MOT_ASSERT(oldVersion != nullptr); + if (oldVersion->IsRowDeleted() == false) { + rc = RC_UNIQUE_VIOLATION; + static_cast(sentinel)->GetGcInfo().Release(); + return nullptr; + } + primary_csn = oldVersion->GetCommitSequenceNumber(); + static_cast(sentinel)->GetGcInfo().Release(); + break; + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + node = static_cast(sentinel)->GetTopNode(); + if (node->GetEndCSN() >= csn) { + rc = RC_UNIQUE_VIOLATION; + return nullptr; + } + // Use for validation + primary_csn = node->GetStartCSN(); + break; + case IndexOrder::INDEX_ORDER_SECONDARY: + SecondarySentinel* s = static_cast(sentinel); + if (s->GetEndCSN() >= csn) { + rc = RC_UNIQUE_VIOLATION; + return nullptr; + } + primary_csn = s->GetStartCSN(); + break; + } + } + // Needed for doing ApplyAddIndexFromOtherTxn + Table* tab = m_txnManager->GetTxnTable(row->GetTable()->GetTableExId()); + if (likely(tab == nullptr)) { + tab = row->GetTable(); + MOT_ASSERT(tab != nullptr); + } + + Access* ac = GetAccessPtr(m_rowCnt); if (unlikely(ac == nullptr)) { - if (!CreateNewAccess(row->GetTable(), type)) { + if (!CreateNewAccess(INS)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create new access entry"); rc = RC::RC_MEMORY_ALLOCATION_ERROR; return nullptr; @@ -279,109 +427,104 @@ Access* TxnAccess::GetNewRowAccess(const Row* row, AccessType type, RC& rc) ac->ResetUsedParameters(); - if (type != INS) { - if (ac->m_localRow == nullptr) { - Row* new_row = m_dummyTable.CreateNewRow(row->GetTable(), ac); - if (__builtin_expect(new_row == nullptr, 0)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create new row"); - rc = RC_MEMORY_ALLOCATION_ERROR; - return nullptr; - } - ac->m_localRow = new_row; - } - - int fieldCount = row->GetTable()->GetFieldCount() - 1; - if (!ac->m_modifiedColumns.IsInitialized()) { - uint8_t* bms = m_dummyTable.CreateBitMapBuffer(fieldCount); - if (__builtin_expect(bms == nullptr, 0)) { - // if modified_columns allocation failed, release allocated row - rc = RC::RC_MEMORY_ALLOCATION_ERROR; - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create columns bitmap"); - m_dummyTable.DestroyRow(ac->m_localRow, ac); - ac->m_localRow = nullptr; - return nullptr; - } - ac->m_modifiedColumns.Init(bms, fieldCount); - } else { - ac->m_modifiedColumns.Reset(fieldCount); - } - - rc = row->GetRow(type, this, ac->m_localRow, last_tid); - if (__builtin_expect(rc != RC_OK, 0)) { - if (rc != RC_ABORT) { // do not log error if aborted due to cc conflict - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Access Row", "Failed to get row"); - } else { - // row can not be fetched due to a conflict, return RC_OK and nullptr - rc = RC_OK; - } - m_dummyTable.DestroyRow(ac->m_localRow, ac); - ac->m_localRow = nullptr; - return nullptr; - } - ac->m_localRow->SetPrimarySentinel(const_cast(row)->GetPrimarySentinel()); - ac->m_params.SetRowCommited(); - ac->m_localRow->SetRowId(row->GetRowId()); - ac->m_localRow->CopySurrogateKey(row); - ac->m_localRow->SetCommitSequenceNumber(last_tid); - } else { - // For inserts local-row is the orig-row - last_tid = row->GetCommitSequenceNumber(); // 0; - ac->m_params.UnsetRowCommited(); - ac->m_localInsertRow = const_cast(row); + if (isUpgrade) { + ac->m_globalRow = oldVersion; + ac->m_csn = primary_csn; + ac->m_snapshot = m_txnManager->GetVisibleCSN(); } - ac->m_type = type; - ac->m_tid = last_tid; + + ac->m_params.UnsetRowCommited(); + ac->m_localInsertRow = row; + ac->m_secondaryUniqueNode = node; + ac->m_type = INS; MOT_LOG_DEBUG("Row Count = %d, access_set_size = %d", m_rowCnt, m_accessSetSize); m_rowCnt++; return ac; } -bool TxnAccess::CreateNewAccess(Table* table, AccessType type) +Access* TxnAccess::GetNewSecondaryAccess(Sentinel* const& originalSentinel, Access* primary_access, RC& rc) +{ + // Create new Access + Access* ac = GetAccessPtr(m_rowCnt); + if (unlikely(ac == nullptr)) { + if (!CreateNewAccess(primary_access->m_type)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create new access entry"); + rc = RC::RC_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + ac = GetAccessPtr(m_rowCnt); + } + ac->ResetUsedParameters(); + + ac->m_origSentinel = originalSentinel; + ac->m_globalRow = primary_access->GetGlobalVersion(); + ac->m_params.SetRowCommited(); + ac->m_params.SetSecondarySentinel(); + ac->m_type = primary_access->m_type; + if (ac->m_type == WR or ac->m_type == DEL) { + ac->m_localRow = primary_access->GetLocalVersion(); + } + ac->m_stmtCount = m_txnManager->GetStmtCount(); + ac->m_redoStmt = ac->m_stmtCount; + MOT_LOG_DEBUG("Row Count = %d, access_set_size = %d", m_rowCnt, m_accessSetSize); + m_rowCnt++; + return ac; +} + +Access* TxnAccess::GetNewRowAccess(Sentinel* const& originalSentinel, AccessType type, RC& rc) +{ + rc = RC_OK; + PrimarySentinel* ps = GetVisiblePrimarySentinel(originalSentinel, m_txnManager->GetVisibleCSN()); + if (ps == nullptr) { + /* Key is not Visible from Secondary index */ + return nullptr; + } + + // Extract the visible row MVCC! + Row* row = GetVisibleRow(ps, type, rc); + if (row == nullptr) { + return nullptr; + } + + Access* ac = GetAccessPtr(m_rowCnt); + if (unlikely(ac == nullptr)) { + if (!CreateNewAccess(type)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create new access entry"); + rc = RC::RC_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + ac = GetAccessPtr(m_rowCnt); + } + ac->ResetUsedParameters(); + + ac->m_origSentinel = ps; + ac->m_globalRow = row; + + ac->m_params.SetRowCommited(); + ac->m_csn = row->GetCommitSequenceNumber(); + ac->m_snapshot = m_txnManager->GetVisibleCSN(); + ac->m_type = type; + MOT_LOG_DEBUG("Row Count = %d, access_set_size = %d", m_rowCnt, m_accessSetSize); + m_rowCnt++; + return ac; +} + +bool TxnAccess::CreateNewAccess(AccessType type) { // row_cnt should be smaller than the array size if (unlikely(m_rowCnt >= (m_accessSetSize - 1))) { bool rc = ReallocAccessSet(); - if (rc == false) { + if (!rc) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create Access", "Failed to reallocate access set"); return rc; } } - m_accessesSetBuff[m_rowCnt] = new (std::nothrow) Access(m_rowCnt); + m_accessesSetBuff[m_rowCnt] = AllocNewAccess(m_rowCnt); Access* ac = GetAccessPtr(m_rowCnt); if (__builtin_expect(ac == nullptr, 0)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Create Access", "Failed to get access pointer"); return false; } - - // Must create special Row object with maximal 'data' payload since this - // Row object can be reused by different getRow() calls for different Tables. - if (type != AccessType::INS) { - Row* new_row = m_dummyTable.CreateNewRow(table, ac); - if (__builtin_expect(new_row == nullptr, 0)) { - delete ac; - m_accessesSetBuff[m_rowCnt] = nullptr; - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create Access", "Failed to create new row"); - return false; - } - new_row->m_table = table; - ac->m_localRow = new_row; - - int fieldCount = table->GetFieldCount() - 1; - uint8_t* bms = m_dummyTable.CreateBitMapBuffer(fieldCount); - if (__builtin_expect(bms == nullptr, 0)) { - // if modified_columns allocation failed, release allocated row - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create Access", "Failed to create columns bitmap"); - m_dummyTable.DestroyRow(ac->m_localRow, ac); - ac->m_localRow = nullptr; - delete ac; - m_accessesSetBuff[m_rowCnt] = nullptr; - return false; - } - ac->m_modifiedColumns.Init(bms, fieldCount); - } else { - ac->m_localRow = nullptr; - } - m_allocatedAc++; return true; } @@ -397,90 +540,28 @@ Access* TxnAccess::RowLookup(void* const currentKey) SetLastAccess(current_access); return current_access; } + SetLastAccess(nullptr); return nullptr; } -RC TxnAccess::AccessLookup(const AccessType type, Sentinel* const originalSentinel, Row*& r_local_Row) +RC TxnAccess::CheckDuplicateInsert(Sentinel* const sentinel) { - if (m_rowCnt == 0) { return RC::RC_LOCAL_ROW_NOT_FOUND; } // type is the external operation.. for access the operation is always RD - void* key = nullptr; Access* curr_acc = nullptr; - - /* - * 2-Level caching - * Minimum is 2 cache misses for first access. - * For second mapped access Worse case 2 misses, - * Average 1 cache miss. - * Search:try look for row - if row not found search for sentinel!(INS type) - * If Row found and not delete return the row - * If Row is in Del verify we dont have an INS on top of the DEL - */ - if (originalSentinel->IsCommited()) { - key = (void*)originalSentinel; - curr_acc = RowLookup(key); - // Maybe Our Insert got commited! - if (curr_acc == nullptr) { - key = originalSentinel->GetPrimarySentinel(); - curr_acc = RowLookup(key); - } - } else { - key = (void*)originalSentinel; - curr_acc = RowLookup(key); - } - r_local_Row = nullptr; + curr_acc = RowLookup(sentinel); if (curr_acc != nullptr) { // Filter rows switch (curr_acc->m_type) { case AccessType::RD: - if (m_txnManager->GetTxnIsoLevel() == READ_COMMITED) { - // If Cached row is not valid, remove it!! - if (type == RD and curr_acc->m_stmtCount != m_txnManager->GetStmtCount()) { - auto it = m_rowsSet->find(curr_acc->m_origSentinel); - MOT_ASSERT(it != m_rowsSet->end()); - m_rowsSet->erase(it); - ReleaseAccess(curr_acc); - return RC::RC_LOCAL_ROW_NOT_FOUND; - } - } - break; case AccessType::RD_FOR_UPDATE: case AccessType::WR: + case AccessType::INS: break; case AccessType::DEL: return RC::RC_LOCAL_ROW_DELETED; - break; - case AccessType::INS: - if (m_txnManager->GetStmtCount() != 0 && curr_acc->m_stmtCount == m_txnManager->GetStmtCount()) { - return RC::RC_LOCAL_ROW_NOT_VISIBLE; - } - // If current state is insert and next state is delete - // do not alloacte new row - if (curr_acc->m_localRow == nullptr and (type == AccessType::WR or type == AccessType::RD_FOR_UPDATE)) { - Table* table = curr_acc->GetRowFromHeader()->GetTable(); - Row* new_row = m_dummyTable.CreateNewRow(table, curr_acc); - if (__builtin_expect(new_row == nullptr, 0)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Lookup Access", "Failed to create new row"); - return RC_MEMORY_ALLOCATION_ERROR; - } - - int fieldCount = table->GetFieldCount() - 1; - uint8_t* bms = m_dummyTable.CreateBitMapBuffer(fieldCount); - if (__builtin_expect(bms == nullptr, 0)) { - // if modified_columns allocation failed, release allocated row - m_dummyTable.DestroyRow(new_row, curr_acc); - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Lookup Access", "Failed to create columns bitmap"); - return RC::RC_MEMORY_ALLOCATION_ERROR; - } - - new_row->m_table = table; - curr_acc->m_localRow = new_row; - curr_acc->m_modifiedColumns.Init(bms, fieldCount); - } - break; default: break; } @@ -488,6 +569,123 @@ RC TxnAccess::AccessLookup(const AccessType type, Sentinel* const originalSentin return RC::RC_LOCAL_ROW_NOT_FOUND; } + return RC::RC_LOCAL_ROW_FOUND; +} + +RC TxnAccess::AccessLookup(const AccessType type, Sentinel* const originalSentinel, Row*& r_local_Row) +{ + if (m_rowCnt == 0) { + return RC::RC_LOCAL_ROW_NOT_FOUND; + } + // type is the external operation.. for access the operation is always RD + Access* curr_acc = nullptr; + + bool isCommitted = originalSentinel->IsCommited(); + IndexOrder order = originalSentinel->GetIndexOrder(); + if (!isCommitted) { + curr_acc = RowLookup(originalSentinel); + } else { + switch (order) { + case IndexOrder::INDEX_ORDER_PRIMARY: + curr_acc = RowLookup(originalSentinel); + break; + case IndexOrder::INDEX_ORDER_SECONDARY: + curr_acc = RowLookup(originalSentinel); + if (curr_acc == nullptr) { + curr_acc = RowLookup(originalSentinel->GetPrimarySentinel()); + if (curr_acc) { + // Lets Verify the relation between Primary/Secondary Key using the Snapshot + if (GetVisibleRowVersion(originalSentinel, curr_acc->m_snapshot) == nullptr) { + curr_acc = nullptr; + return RC::RC_LOCAL_ROW_NOT_VISIBLE; + } + } + } + break; + case IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE: + curr_acc = RowLookup(originalSentinel); + if (curr_acc == nullptr) { + PrimarySentinelNode* node = nullptr; + if (m_txnManager->GetIsolationLevel() > ISOLATION_LEVEL::READ_COMMITED) { + node = static_cast(originalSentinel) + ->GetNodeByCSN(m_txnManager->GetVisibleCSN()); + if (node != nullptr) { + curr_acc = RowLookup(node->GetPrimarySentinel()); + } + } else { + node = static_cast(originalSentinel)->GetTopNode(); + while (node != nullptr) { + curr_acc = RowLookup(node->GetPrimarySentinel()); + if (curr_acc) { + Row* r = GetVisibleRowVersion(originalSentinel, curr_acc->m_snapshot); + if (r == nullptr or r->GetCommitSequenceNumber() != curr_acc->m_csn) { + curr_acc = nullptr; + return RC::RC_LOCAL_ROW_NOT_VISIBLE; + } + break; + } + node = node->GetNextVersion(); + } + } + } + break; + default: + MOT_ASSERT(false); + break; + } + } + + return LookupFilterRow(curr_acc, type, isCommitted, r_local_Row); +} + +RC TxnAccess::LookupFilterRow(Access* curr_acc, const AccessType type, bool isCommitted, Row*& r_local_Row) +{ + r_local_Row = nullptr; + if (curr_acc != nullptr) { + // Filter rows + switch (curr_acc->m_type) { + case AccessType::RD: + // Cached row is not valid remove it!! + if (curr_acc->m_stmtCount != m_txnManager->GetStmtCount()) { + auto it = m_rowsSet->find(curr_acc->m_origSentinel); + MOT_ASSERT(it != m_rowsSet->end()); + auto res = m_rowsSet->erase(it); + // need to perform index clean-up! + ReleaseAccess(curr_acc); + return RC::RC_LOCAL_ROW_NOT_FOUND; + } + break; + case AccessType::RD_FOR_UPDATE: + case AccessType::WR: + break; + case AccessType::INS: + if (curr_acc->m_stmtCount == m_txnManager->GetStmtCount() && type != AccessType::WR) { + return RC::RC_LOCAL_ROW_NOT_VISIBLE; + } + if (curr_acc->GetSentinel()->GetIndexOrder() != IndexOrder::INDEX_ORDER_PRIMARY) { + Access* primaryAccess = RowLookup(curr_acc->GetTxnRow()->GetPrimarySentinel()); + if (primaryAccess == nullptr) { + return RC::RC_PRIMARY_SENTINEL_NOT_MAPPED; + } else { + r_local_Row = primaryAccess->GetTxnRow(); + return RC::RC_LOCAL_ROW_FOUND; + } + } + break; + case AccessType::DEL: + return RC::RC_LOCAL_ROW_DELETED; + default: + MOT_ASSERT(false); + break; + } + } else { + if (isCommitted) { + return RC::RC_LOCAL_ROW_NOT_FOUND; + } else { + return RC::RC_LOCAL_ROW_NOT_VISIBLE; + } + } + r_local_Row = curr_acc->GetTxnRow(); return RC::RC_LOCAL_ROW_FOUND; } @@ -497,20 +695,23 @@ Row* TxnAccess::MapRowtoLocalTable(const AccessType type, Sentinel* const& origi Access* current_access = nullptr; rc = RC_OK; - current_access = GetNewRowAccess(originalSentinel->GetData(), type, rc); + current_access = GetNewRowAccess(originalSentinel, type, rc); // Check if draft is valid if (current_access == nullptr) return nullptr; - // Set Last access - SetLastAccess(current_access); - current_access->m_origSentinel = reinterpret_cast(originalSentinel->GetPrimarySentinel()); - + current_access->m_type = (type != RD_FOR_UPDATE) ? RD : RD_FOR_UPDATE; current_access->m_params.SetPrimarySentinel(); + // We map the p_sentinel for the case of commited Row! void* key = (void*)current_access->m_origSentinel; - m_rowsSet->insert(RowAccessPair_t(key, current_access)); - return current_access->m_localRow; + auto ret = m_rowsSet->insert(RowAccessPair_t(key, current_access)); + MOT_ASSERT(ret.second == true); + // Set Last access + SetLastAccess(current_access); + current_access->m_stmtCount = m_txnManager->GetStmtCount(); + current_access->m_redoStmt = current_access->m_stmtCount; + return current_access->GetGlobalVersion(); } Row* TxnAccess::AddInsertToLocalAccess(Sentinel* org_sentinel, Row* org_row, RC& rc, bool isUpgrade) @@ -521,56 +722,84 @@ Row* TxnAccess::AddInsertToLocalAccess(Sentinel* org_sentinel, Row* org_row, RC& // Search key from the unordered_map auto search = m_rowsSet->find(org_sentinel); if (likely(search == m_rowsSet->end())) { - if (isUpgrade == true) { - curr_access = GetNewRowAccess(org_sentinel->GetData(), INS, rc); + if (isUpgrade) { + curr_access = GetNewInsertAccess(org_sentinel, org_row, rc, isUpgrade); // Check if draft is valid if (curr_access == nullptr) { return nullptr; } curr_access->m_params.SetUpgradeInsert(); - // First row in the transaction, updrade is on deleted commited row - // Deleter did not remove the row due to checkpoint or concurrent inserter - // DummyDeletedRow - No need to serialize the row - curr_access->m_params.SetDummyDeletedRow(); - // First Insert in the transaction - curr_access->m_auxRow = org_row; + // Not in the cache - insert on deleted key + curr_access->m_params.SetInsertOnDeletedRow(); } else { - curr_access = GetNewRowAccess(org_row, INS, rc); + curr_access = GetNewInsertAccess(org_sentinel, org_row, rc, isUpgrade); // Check if draft is valid if (curr_access == nullptr) { return nullptr; } - curr_access->m_stmtCount = m_txnManager->GetStmtCount(); } - + curr_access->m_stmtCount = m_txnManager->GetStmtCount(); + curr_access->m_redoStmt = curr_access->m_stmtCount; curr_access->m_origSentinel = org_sentinel; if (org_sentinel->IsPrimaryIndex()) { curr_access->m_params.SetPrimarySentinel(); } else { curr_access->m_params.SetSecondarySentinel(); if (org_sentinel->GetIndex()->GetUnique()) { + Index* index = org_sentinel->GetIndex(); + PrimarySentinelNode* node = index->SentinelNodeAlloc(); + if (node == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Sentinel node", "Failed to create new sentinel entry"); + rc = RC::RC_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + (void)m_sentinelObjectPool->insert({index, node}); curr_access->m_params.SetUniqueIndex(); } } - m_rowsSet->insert(RowAccessPair_t(org_sentinel, curr_access)); + auto ret = m_rowsSet->insert(RowAccessPair_t(org_sentinel, curr_access)); + MOT_ASSERT(ret.second == true); + SetLastAccess(curr_access); return curr_access->GetTxnRow(); - } else { - Access* curr_access = search->second; + curr_access = search->second; // Promote state of row to Delete if (curr_access->m_type == DEL) { - // If we found a deleted row and the sentinel is not commited! + // If we found a deleted row and the sentinel is not committed! // the row was deleted in between statement - we must abort! - if (isUpgrade == false) { - MOT_LOG_ERROR("Invalid Insert! Row was deleted before i was able to delete!\n") + if (!isUpgrade) { + MOT_LOG_ERROR("Invalid Insert! Row was deleted before i was able to delete!") rc = RC_UNIQUE_VIOLATION; return nullptr; } + if (org_sentinel->GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE) { + PrimarySentinelNode* topNode = + static_cast(curr_access->m_origSentinel)->GetTopNode(); + if ((curr_access->m_secondaryUniqueNode != topNode) or + (topNode->GetEndCSN() != Sentinel::SENTINEL_INIT_CSN)) { + MOT_LOG_ERROR("Invalid Insert! Key already changed") + rc = RC_UNIQUE_VIOLATION; + return nullptr; + } + Index* index = org_sentinel->GetIndex(); + PrimarySentinelNode* node = index->SentinelNodeAlloc(); + if (node == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Sentinel node", "Failed to create new sentinel entry"); + rc = RC::RC_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + (void)m_sentinelObjectPool->insert({index, node}); + } + if (curr_access->m_secondaryDelKey != nullptr) { + org_sentinel->GetIndex()->DestroyKey(curr_access->m_secondaryDelKey); + curr_access->m_secondaryDelKey = nullptr; + } curr_access->m_type = INS; curr_access->m_params.SetUpgradeInsert(); - // row to be commited - curr_access->m_auxRow = org_row; + curr_access->m_localInsertRow = org_row; + curr_access->m_stmtCount = m_txnManager->GetStmtCount(); + SetLastAccess(curr_access); return curr_access->GetTxnRow(); } } @@ -578,55 +807,41 @@ Row* TxnAccess::AddInsertToLocalAccess(Sentinel* org_sentinel, Row* org_row, RC& return nullptr; } -static const char* const enTxnStates[] = {stringify(INV), - stringify(RD), - stringify(RD_FOR_UPDATE), - stringify(WR), - stringify(DEL), - stringify(INS), - stringify(SCAN), - stringify(TEST)}; +enum NS_ACTIONS : uint32_t { NOCHANGE, GENERATE_DRAFT, FILTER_DELETES, NS_ERROR }; -enum NS_ACTIONS : uint32_t { NOCHANGE, INC_WRITES, FILTER_DELETES, GENERATE_ACCESS, NS_ERROR }; +typedef struct { + uint32_t nextState; + uint32_t action; +} TxnStateMachineEntry; -typedef union { - struct { - uint64_t next_state : 32; - - uint64_t action : 32; - } entry; - - uint64_t val; -} Table_Entry; - -static const Table_Entry txnStateMachine[TSM_SIZE][TSM_SIZE] = { +static const TxnStateMachineEntry txnStateMachine[TxnAccess::TSM_SIZE][TxnAccess::TSM_SIZE] = { /* INVALID STATE */ {{INV, NS_ACTIONS::NS_ERROR}, // INV {RD, NS_ACTIONS::NOCHANGE}, // RD {RD_FOR_UPDATE, NS_ACTIONS::NOCHANGE}, // RD_FOT_UPDATE - {WR, NS_ACTIONS::INC_WRITES}, // WR - {DEL, NS_ACTIONS::GENERATE_ACCESS}, // DEL - {INS, NS_ACTIONS::INC_WRITES}}, // INS + {WR, NS_ACTIONS::GENERATE_DRAFT}, // WR + {DEL, NS_ACTIONS::GENERATE_DRAFT}, // DEL + {INS, NS_ACTIONS::NOCHANGE}}, // INS /* READ STATE */ {{RD, NS_ACTIONS::NS_ERROR}, {RD, NS_ACTIONS::NOCHANGE}, {RD_FOR_UPDATE, NS_ACTIONS::NOCHANGE}, - {WR, NS_ACTIONS::INC_WRITES}, - {DEL, NS_ACTIONS::GENERATE_ACCESS}, + {WR, NS_ACTIONS::GENERATE_DRAFT}, + {DEL, NS_ACTIONS::GENERATE_DRAFT}, {RD, NS_ACTIONS::NS_ERROR}}, /* READ_FOR_UPDATE STATE */ {{RD_FOR_UPDATE, NS_ACTIONS::NS_ERROR}, {RD_FOR_UPDATE, NS_ACTIONS::NOCHANGE}, - {RD_FOR_UPDATE, NS_ACTIONS::NOCHANGE}, - {WR, NS_ACTIONS::INC_WRITES}, - {DEL, NS_ACTIONS::GENERATE_ACCESS}, + {RD_FOR_UPDATE, NS_ACTIONS::GENERATE_DRAFT}, + {WR, NS_ACTIONS::GENERATE_DRAFT}, + {DEL, NS_ACTIONS::GENERATE_DRAFT}, {RD_FOR_UPDATE, NS_ACTIONS::NS_ERROR}}, /* WRITE STATE */ {{WR, NS_ACTIONS::NS_ERROR}, {WR, NS_ACTIONS::NOCHANGE}, {WR, NS_ACTIONS::NOCHANGE}, {WR, NS_ACTIONS::NOCHANGE}, - {DEL, NS_ACTIONS::GENERATE_ACCESS}, + {DEL, NS_ACTIONS::GENERATE_DRAFT}, {WR, NS_ACTIONS::NS_ERROR}}, /* DELETE STATE */ {{DEL, NS_ACTIONS::NS_ERROR}, @@ -647,38 +862,42 @@ static const Table_Entry txnStateMachine[TSM_SIZE][TSM_SIZE] = { // Currently supporting RD/WR transitions RC TxnAccess::UpdateRowState(AccessType type, Access* ac) { - RC rc = RC_OK; MOT_LOG_DEBUG("Switch key State from: %s to: %s", enTxnStates[ac->m_type], enTxnStates[type]); - AccessType current_state = ac->m_type; + AccessType currentState = ac->m_type; - auto result = txnStateMachine[current_state][type]; - if (unlikely(result.entry.action == NS_ACTIONS::NS_ERROR)) { - MOT_LOG_ERROR("Invalid State: current_state=%d, type=%d\n", (int)current_state, (int)type); + auto result = txnStateMachine[currentState][type]; + if (unlikely(result.action == NS_ACTIONS::NS_ERROR)) { + MOT_LOG_ERROR("Invalid State: current_state=%u, type=%u", currentState, type); return RC_ERROR; } - ac->m_type = (AccessType)result.entry.next_state; - bool res = false; - NS_ACTIONS action = (NS_ACTIONS)result.entry.action; + RC rc = RC_OK; + AccessType currentType = ac->m_type; + ac->m_type = (AccessType)result.nextState; + NS_ACTIONS action = (NS_ACTIONS)result.action; switch (action) { case NS_ACTIONS::FILTER_DELETES: // Check and filter the access_set for cases of delete after insert in the same transaction. - res = FilterOrderedSet(ac); + (void)FilterOrderedSet(ac); break; - case NS_ACTIONS::GENERATE_ACCESS: - rc = GenerateDeletes(ac); - break; - case NS_ACTIONS::INC_WRITES: + case NS_ACTIONS::GENERATE_DRAFT: + rc = GenerateDraft(ac, currentType); + if (rc != RC_OK) { + break; + } + if (ac->m_type == DEL) { + rc = GenerateSecondaryAccess(ac, ac->m_type); + } MOT_LOG_DEBUG( - "ACTION REQUIRED Switch key state from:%s, to:%s", enTxnStates[current_state], enTxnStates[type]); + "ACTION REQUIRED Switch key state from: %s, to: %s", enTxnStates[currentState], enTxnStates[type]); break; case NS_ACTIONS::NOCHANGE: MOT_LOG_DEBUG( - "NO ACTION REQUIRED Switch key state from:%s, to:%s", enTxnStates[current_state], enTxnStates[type]); + "NO ACTION REQUIRED Switch key state from: %s, to: %s", enTxnStates[currentState], enTxnStates[type]); break; case NS_ACTIONS::NS_ERROR: MOT_LOG_DEBUG( - "ERROR in state Switch key state from:%s, to:%s", enTxnStates[current_state], enTxnStates[type]); + "ERROR in state Switch key state from: %s, to: %s", enTxnStates[currentState], enTxnStates[type]); rc = RC_ERROR; break; } @@ -686,57 +905,312 @@ RC TxnAccess::UpdateRowState(AccessType type, Access* ac) return rc; } -Row* TxnAccess::GetReadCommitedRow(Sentinel* sentinel) +Row* TxnAccess::GetVisibleRowVersion(Sentinel* sentinel, uint64_t csn) { - TransactionId last_tid; - if (likely(sentinel->IsCommited() == true)) { - Row* row = sentinel->GetData(); - RC rc = row->GetRow(AccessType::RD, this, m_rowZero, last_tid); - if (rc != RC::RC_OK) { - return nullptr; - } else { - m_rowZero->SetCommitSequenceNumber(last_tid); - m_rowZero->SetPrimarySentinel(row->GetPrimarySentinel()); - return m_rowZero; + Row* row = sentinel->GetVisibleRowVersion(csn); + if (row) { + if (row->IsRowDeleted() == false) { + return row; } - } else - return nullptr; + } + return nullptr; } -RC TxnAccess::GenerateDeletes(Access* element) + +PrimarySentinel* TxnAccess::GetVisiblePrimarySentinel(Sentinel* sentinel, uint64_t csn) { - RC rc = RC_OK; - Sentinel* outSentinel = nullptr; - MaxKey key; - Row* row = element->GetTxnRow(); - Table* table = row->GetTable(); + return static_cast(sentinel->GetVisiblePrimaryHeader(csn)); +} + +Row* TxnAccess::GetVisibleRow(PrimarySentinel* sentinel, AccessType type, RC& rc) +{ + Row* row = nullptr; + rc = RC_OK; + ISOLATION_LEVEL isolationLevel = m_txnManager->GetIsolationLevel(); + uint64_t csn = m_txnManager->GetVisibleCSN(); + if (type == RD) { + return sentinel->GetVisibleRowVersion(csn); + } else { + switch (isolationLevel) { + case READ_COMMITED: + row = sentinel->GetVisibleRowForUpdate(csn, isolationLevel); + break; + case REPEATABLE_READ: + row = sentinel->GetVisibleRowForUpdate(csn, isolationLevel); + if (row) { + if (row->GetCommitSequenceNumber() >= csn) { + row = nullptr; + rc = RC_SERIALIZATION_FAILURE; + } + } else { + if (sentinel->GetStartCSN() >= csn) { + rc = RC_SERIALIZATION_FAILURE; + } + } + break; + case SERIALIZABLE: + break; + default: + break; + } + } + + if (row and row->IsRowDeleted()) { + return nullptr; + } + + return row; +} + +RC TxnAccess::SecondaryAccessDeleteTxnRows(Row* row, Table* table) +{ + Key* key = m_txnManager->GetLocalKey(); uint16_t numIndexes = table->GetNumIndexes(); for (uint16_t i = 1; i < numIndexes; i++) { + Sentinel* outSentinel = nullptr; Index* ix = table->GetIndex(i); - key.InitKey(ix->GetKeyLength()); - ix->BuildKey(table, row, &key); - rc = table->FindRowByIndexId(ix, &key, outSentinel, m_txnManager->GetThdId()); + key->InitKey(ix->GetKeyLength()); + ix->BuildKey(table, row, key); + RC rc = table->FindRowByIndexId(ix, key, outSentinel, m_txnManager->GetThdId()); if (rc != RC_OK) { // row already deleted!! Abort m_txnManager->m_err = RC_SERIALIZATION_FAILURE; return RC_SERIALIZATION_FAILURE; } - // generate New Accesses! - Access* current_access = GetNewRowAccess(element->GetRowFromHeader(), DEL, rc); - if (current_access == nullptr) { + auto it = m_rowsSet->find(outSentinel); + // Filter Rows + if (it != m_rowsSet->end()) { + Access* ac = (*it).second; + if (ac->m_type == DEL) { + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + return RC_SERIALIZATION_FAILURE; + } + // Use primary row for rollback + // In some cases we created an Index we cannot rely on access row + ac->m_localInsertRow = row; + m_txnManager->RollbackInsert(ac); + (void)m_rowsSet->erase(it); + // need to perform index clean-up! + ReleaseAccess(ac); + } + } + + return RC_OK; +} + +RC TxnAccess::SecondaryAccessDeleteGlobalRows(Access* primaryAccess, Row* row, Table* table) +{ + RC rc = RC_OK; + Access* current_access = nullptr; + Key* key = m_txnManager->GetLocalKey(); + uint64_t csn = m_txnManager->GetVisibleCSN(); + uint16_t numIndexes = table->GetNumIndexes(); + for (uint16_t i = 1; i < numIndexes; i++) { + current_access = nullptr; + Sentinel* outSentinel = nullptr; + Index* ix = table->GetIndex(i); + key->InitKey(ix->GetKeyLength()); + ix->BuildKey(table, row, key); + rc = table->FindRowByIndexId(ix, key, outSentinel, m_txnManager->GetThdId()); + if (rc != RC_OK) { + // row already deleted!! Abort + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + return RC_SERIALIZATION_FAILURE; + } + auto it = m_rowsSet->find(outSentinel); + // Filter Rows + if (it != m_rowsSet->end()) { + Access* ac = (*it).second; + if (ac->m_params.IsIndexUpdate() and ac->m_type == DEL) { + ac->m_params.UnsetIndexUpdate(); + ac->m_localRow = primaryAccess->m_localRow; + if (ac->m_secondaryDelKey != nullptr) { + ix->DestroyKey(ac->m_secondaryDelKey); + ac->m_secondaryDelKey = nullptr; + } + continue; + } + if (ac->m_type == DEL) { + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + return RC_SERIALIZATION_FAILURE; + } + // Use primary row for rollback + // In some cases we created an Index we cannot rely on access row + ac->m_localInsertRow = row; + m_txnManager->RollbackInsert(ac); + (void)m_rowsSet->erase(it); + // need to perform index clean-up! + ReleaseAccess(ac); + } else { + if (m_txnManager->GetIsolationLevel() > READ_COMMITED) { + PrimarySentinel* ps = outSentinel->GetVisiblePrimaryHeader(csn); + if (ps == nullptr) { + // Sentinel is not visible for current transaction abort! + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + return RC_SERIALIZATION_FAILURE; + } + } + // generate New Accesses! + current_access = GetNewSecondaryAccess(outSentinel, primaryAccess, rc); + if (current_access == nullptr) { + return rc; + } + + if (ix->GetUnique()) { + current_access->m_params.SetUniqueIndex(); + current_access->m_secondaryUniqueNode = + static_cast(outSentinel)->GetNodeByCSN(csn); + if ((current_access->m_secondaryUniqueNode == nullptr) || + (current_access->m_secondaryUniqueNode->GetEndCSN() != Sentinel::SENTINEL_INIT_CSN)) { + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + break; + } + current_access->m_csn = current_access->m_secondaryUniqueNode->GetStartCSN(); + } + auto ret = m_rowsSet->insert(RowAccessPair_t(outSentinel, current_access)); + MOT_ASSERT(ret.second == true); + } + } + + // In case of error destroy the access + if (rc != RC_OK and current_access != nullptr) { + ReleaseAccess(current_access); + current_access = nullptr; + } + + return rc; +} + +RC TxnAccess::GenerateSecondaryAccess(Access* primaryAccess, AccessType acType) +{ + Row* row = primaryAccess->GetTxnRow(); + Table* table = row->GetTable(); + if (m_txnManager->m_txnDdlAccess->Size() > 0 && !table->IsTxnTable()) { + Table* tmp = m_txnManager->GetTxnTable(table->GetTableExId()); + if (tmp != nullptr) { + table = tmp; + } + } + uint16_t numIndexes = table->GetNumIndexes(); + if (numIndexes == 1) { + return RC_OK; + } + + bool isIndexUpdate = false; + + // Transactional row is a tombstone in the case of delete + if (primaryAccess->m_params.IsUpdateDeleted() == true) { + // Need to remove all keys before and after the update + // Old keys are in IndexUpdate, new keys are inserts + row = m_rowZero; + primaryAccess->m_params.UnsetUpdateDeleted(); + if (primaryAccess->m_params.IsIndexUpdate() == true) { + primaryAccess->m_params.UnsetIndexUpdate(); + isIndexUpdate = true; + } + } else { + MOT_ASSERT(row->GetRowType() == RowType::TOMBSTONE); + row = GetRowZeroCopyIfAny(primaryAccess->GetGlobalVersion()); + } + + if (unlikely(isIndexUpdate)) { + // first phase go through updated row and filter new rows + RC rc = SecondaryAccessDeleteTxnRows(row, table); + if (rc != RC_OK) { return rc; } - current_access->m_origSentinel = outSentinel; - current_access->m_params.SetSecondarySentinel(); - if (current_access->m_origSentinel->GetIndex()->GetUnique()) { - current_access->m_params.SetUniqueIndex(); - } - m_rowsSet->insert(RowAccessPair_t(outSentinel, current_access)); + + // Switch back to global row! + row = GetRowZeroCopyIfAny(primaryAccess->GetGlobalVersion()); + } + + return SecondaryAccessDeleteGlobalRows(primaryAccess, row, table); +} + +RC TxnAccess::GenerateDraft(Access* ac, AccessType acType) +{ + RC rc = RC_OK; + MOT_ASSERT(ac->GetGlobalVersion() != nullptr); + switch (ac->m_type) { + case DEL: + case WR: + return CreateMVCCDraft(ac, acType); + case RD_FOR_UPDATE: + default: + break; } return rc; } + +Row* TxnAccess::FetchRowFromPrimarySentinel(const AccessType type, Sentinel* const& originalSentinel, RC& rc) +{ + rc = RC_OK; + MOT_ASSERT(originalSentinel->GetIndexOrder() != IndexOrder::INDEX_ORDER_PRIMARY); + Access* current_access = RowLookup(originalSentinel); + MOT_ASSERT(current_access != nullptr); + Sentinel* primarySentinel = current_access->GetTxnRow()->GetPrimarySentinel(); + MOT_ASSERT(primarySentinel != nullptr); + if (type == RD) { + return GetVisibleRowVersion(primarySentinel, m_txnManager->GetVisibleCSN()); + } else { + return MapRowtoLocalTable(type, primarySentinel, rc); + } +} + +RC TxnAccess::CreateMVCCDraft(Access* ac, AccessType acType) +{ + if (ac->m_localRow != nullptr) { + // If we already generated a draft for update + // Lets replace it with a draft for DEL + if (acType == WR and ac->m_type == DEL) { + MOT_ASSERT(ac->m_localRow != nullptr); + // Indicate that DELETE should use rowZero + ac->m_params.SetUpdateDeleted(); + GetRowZeroCopy(ac->m_localRow); + ac->m_localRow->GetTable()->DestroyRow(ac->m_localRow); + ac->m_localRow = nullptr; + if (ac->m_modifiedColumns.IsInitialized()) { + m_dummyTable.DestroyBitMapBuffer(ac->m_modifiedColumns.GetData(), ac->m_modifiedColumns.GetSize()); + ac->m_modifiedColumns.Reset(); + } + } else { + MOT_ASSERT(false); + return RC_ABORT; + } + } + + Row* globalRow = ac->GetGlobalVersion(); + Table* tab = m_txnManager->GetTxnTable(globalRow->GetTable()->GetTableExId()); + if (likely(tab == nullptr)) { + tab = globalRow->GetTable(); + } + Row* newRow = tab->CreateNewRowCopy(globalRow, ac->m_type); + if (__builtin_expect(newRow == nullptr, 0)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to copy or create new row"); + return RC_MEMORY_ALLOCATION_ERROR; + } + newRow->SetTable(tab); + if (ac->m_type != DEL) { + int fieldCount = tab->GetFieldCount() - 1; + RC rc = InitBitMap(ac, fieldCount); + if (rc != RC_OK) { + DestroyAccess(ac); + tab->DestroyRow(newRow); + return rc; + } + } + + ac->m_localRow = newRow; + ac->m_localRow->SetPrimarySentinel(ac->m_origSentinel); + ac->m_localRow->SetCommitSequenceNumber(Sentinel::SENTINEL_INVISIBLE_CSN); + ac->m_localRow->SetNextVersion(nullptr); + + IncreaseTableStat(tab); + + return RC_OK; +} bool TxnAccess::FilterOrderedSet(Access* element) { - RC rc = RC_OK; bool res = false; // no need to filter current delete // the delete is performed on an index item @@ -746,43 +1220,530 @@ bool TxnAccess::FilterOrderedSet(Access* element) MOT_ASSERT(element->m_type == DEL); // revert type to INS element->m_type = INS; - MaxKey max_key; Sentinel* outSentinel = nullptr; - Key* key = nullptr; + Row* releasedRow = nullptr; + Key* key = m_txnManager->GetLocalKey(); // there are other indices elements that need to be removed. // case of BEGIN->INS->DEL - // For blocked INSERTS the key is the orig_sentienl and the shared element is the orig_row! + // For blocked INSERTS the key is the orig_sentinel and the shared element is the orig_row! // Lets get all the keys and remove them from the cache and try to remove from the Index if possible Row* row = element->GetTxnRow(); Table* table = row->GetTable(); + if (m_txnManager->m_txnDdlAccess->Size() > 0 && !table->IsTxnTable()) { + Table* tmp = m_txnManager->GetTxnTable(table->GetTableExId()); + if (tmp != nullptr) { + table = tmp; + } + } uint16_t numIndexes = table->GetNumIndexes(); for (uint16_t i = 0; i < numIndexes; i++) { + outSentinel = nullptr; Index* ix = table->GetIndex(i); - key = &max_key; key->InitKey(ix->GetKeyLength()); ix->BuildKey(table, row, key); - rc = table->FindRowByIndexId(ix, key, outSentinel, m_txnManager->GetThdId()); + (void)table->FindRowByIndexId(ix, key, outSentinel, m_txnManager->GetThdId()); auto it = m_rowsSet->find(outSentinel); // Filter Rows if (it != m_rowsSet->end()) { Access* ac = (*it).second; res = true; - rc = m_txnManager->RollbackInsert(ac); - if (ac->m_params.IsUpgradeInsert() == false or ac->m_params.IsDummyDeletedRow() == true) { - m_rowsSet->erase(it); + m_txnManager->RollbackInsert(ac); + releasedRow = ac->m_localInsertRow; + if (ac->m_params.IsUpgradeInsert() == false or ac->m_params.IsInsertOnDeletedRow() == true) { + (void)m_rowsSet->erase(it); // need to perform index clean-up! ReleaseAccess(ac); } else { // Transform INS to DEL ac->m_type = DEL; - MOT_ASSERT(ac->m_auxRow != nullptr); ac->m_params.UnsetUpgradeInsert(); - ac->m_auxRow = nullptr; + ac->m_localInsertRow = nullptr; + ac->m_params.UnsetIndexUpdate(); } } } + if (releasedRow) { + table->DestroyRow(releasedRow); + } } return res; } + +RC TxnAccess::InitBitMap(Access* ac, int fieldCount) +{ + RC rc = RC_OK; + if (!ac->m_modifiedColumns.IsInitialized()) { + uint8_t* bms = m_dummyTable.CreateBitMapBuffer(fieldCount); + if (__builtin_expect(bms == nullptr, 0)) { + // if modified_columns allocation failed, release allocated row + rc = RC::RC_MEMORY_ALLOCATION_ERROR; + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Access Row", "Failed to create columns bitmap"); + DestroyAccess(ac); + return rc; + } + ac->m_modifiedColumns.Init(bms, fieldCount); + } else { + MOT_ASSERT(false); + rc = RC_ABORT; + } + return rc; +} + +void TxnAccess::GcMaintenance() +{ + Row* newVersion = nullptr; + uint64_t csn = m_txnManager->GetCommitSequenceNumber(); + for (const auto& raPair : GetOrderedRowSet()) { + Access* access = raPair.second; + switch (access->m_type) { + case WR: + (void)static_cast(access->m_origSentinel)->GetGcInfo().RefCountUpdate(INC); + newVersion = access->m_localRow; + m_txnManager->GcSessionRecordRcu(GC_QUEUE_TYPE::VERSION_QUEUE, + newVersion->GetTable()->GetPrimaryIndex()->GetIndexId(), + newVersion, + access->m_origSentinel, + Row::RowVersionDtor, + ROW_SIZE_FROM_POOL(newVersion->GetTable()), + csn); + access->m_localRow = nullptr; + break; + case DEL: + if (access->m_params.IsPrimarySentinel() == true) { + if (access->GetSentinel()->GetStable() == nullptr) { + (void)static_cast(access->m_origSentinel)->GetGcInfo().RefCountUpdate(INC); + newVersion = access->m_localRow; + m_txnManager->GcSessionRecordRcu(GC_QUEUE_TYPE::DELETE_QUEUE, + newVersion->GetTable()->GetPrimaryIndex()->GetIndexId(), + newVersion, + access->m_origSentinel, + Row::DeleteRowDtor, + ROW_SIZE_FROM_POOL(newVersion->GetTable()), + csn); + } + access->m_localRow = nullptr; + } else { + if (access->m_params.IsIndexUpdate()) { + MOT_ASSERT(access->m_secondaryDelKey != nullptr); + MOT_ASSERT(access->m_origSentinel->GetCounter() > 0); + m_txnManager->GcSessionRecordRcu(GC_QUEUE_TYPE::UPDATE_COLUMN_QUEUE, + access->m_origSentinel->GetIndex()->GetIndexId(), + access->m_origSentinel, + access->m_secondaryDelKey, + Index::DeleteKeyDtor, + SENTINEL_SIZE(access->m_origSentinel->GetIndex()), + csn); + access->m_secondaryDelKey = nullptr; + } + } + break; + case INS: + if (access->m_params.IsPrimarySentinel() == true) { + // For both upgrades we insert the latest row + // Latest Version will perform the delete + if (access->m_params.IsUpgradeInsert() == true) { + (void)static_cast(access->m_origSentinel)->GetGcInfo().RefCountUpdate(INC); + newVersion = access->GetLocalInsertRow(); + m_txnManager->GcSessionRecordRcu(GC_QUEUE_TYPE::VERSION_QUEUE, + newVersion->GetTable()->GetPrimaryIndex()->GetIndexId(), + newVersion, + access->m_origSentinel, + Row::RowVersionDtor, + ROW_SIZE_FROM_POOL(newVersion->GetTable()), + csn); + access->m_localInsertRow = nullptr; + access->m_localRow = nullptr; + } + } + break; + default: + break; + } + } +} + +Row* TxnAccess::GetRowZeroCopyIfAny(Row* localRow) +{ + Table* rowTable = localRow->GetTable(); + Table* txnTable = m_txnManager->GetTxnTable(rowTable->GetTableExId()); + if (txnTable == nullptr or rowTable->IsTxnTable()) { + return localRow; + } else { + if (txnTable->GetHasColumnChanges()) { + m_rowZero->CopyRowZero(localRow, txnTable); + } else { + return localRow; + } + } + + return m_rowZero; +} + +void TxnAccess::GetRowZeroCopy(Row* localRow) +{ + Table* rowTable = localRow->GetTable(); + Table* txnTable = m_txnManager->GetTxnTable(rowTable->GetTableExId()); + if (txnTable != nullptr) { + m_rowZero->CopyRowZero(localRow, txnTable); + } else { + m_rowZero->CopyRowZero(localRow, rowTable); + } +} + +void TxnAccess::Print() +{ + multimap GlobalMap; + cout << "\nAccess Map THREAD:" << m_txnManager->GetThdId() << endl; + cout << "-----------------------" << endl; + if (m_rowCnt > ACCESS_SET_PRINT_THRESHOLD) { + MOT_LOG_INFO("Access Table too big to print, printing table statistics"); + PrintStats(); + return; + } + + if (m_rowCnt == 0) { + MOT_LOG_INFO("Access Table is empty"); + return; + } + + uint32_t max_table_size = 0; + uint32_t max_index_size = 0; + for (const auto& raPair : GetOrderedRowSet()) { + Access* access = raPair.second; + Table* table = access->GetSentinel()->GetIndex()->GetTable(); + (void)GlobalMap.insert({table->GetTableName(), access}); + if (max_table_size < table->GetTableName().length()) { + max_table_size = table->GetTableName().length(); + } + if (max_index_size < access->GetSentinel()->GetIndex()->GetName().length()) { + max_index_size = access->GetSentinel()->GetIndex()->GetName().length(); + } + } + + auto itr = GlobalMap.begin(); + string table = itr->first; + Access* access = itr->second; + cout << left << setw(max_table_size) << setfill(' ') << "Table"; + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << "Index"; + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << "Index Order"; + cout << '\t' << "| "; + cout << left << setw(13) << setfill(' ') << "Access Type"; + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << "Sentinel" << endl; + cout << "-------------------------------------------------" + << "-----------------------------------------------" << endl; + cout << left << setw(max_table_size) << setfill(' ') << itr->first; + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << access->GetSentinel()->GetIndex()->GetName(); + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') + << enIndexOrder[static_cast(access->GetSentinel()->GetIndexOrder())]; + cout << '\t' << "| "; + cout << left << setw(13) << setfill(' ') << enTxnStates[access->GetType()]; + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << access->GetSentinel() << endl; + (void)++itr; + for (; itr != GlobalMap.end(); (void)++itr) { + if (table == itr->first) { + cout << left << setw(max_table_size) << setfill(' ') << " "; + } else { + cout << "-------------------------------------------------" + << "-----------------------------------------------" << endl; + + cout << left << setw(max_table_size) << setfill(' ') << itr->first; + table = itr->first; + } + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') << (itr->second)->GetSentinel()->GetIndex()->GetName(); + cout << '\t' << "| "; + cout << left << setw(max_index_size) << setfill(' ') + << enIndexOrder[static_cast((itr->second)->GetSentinel()->GetIndexOrder())]; + cout << '\t' << "| "; + cout << left << setw(13) << setfill(' ') << enTxnStates[(itr->second)->GetType()]; + cout << " | "; + cout << left << setw(14) << setfill(' ') << (itr->second)->GetSentinel() << endl; + } + cout << "-------------------------------------------------" + << "-----------------------------------------------" << endl; + PrintStats(); +} + +void TxnAccess::PrintStats() +{ + map> GlobalMap; + uint8_t maxAccessTypes = static_cast(AccessType::INS) + 1; + for (const auto& raPair : GetOrderedRowSet()) { + Access* access = raPair.second; + Table* table = access->GetSentinel()->GetIndex()->GetTable(); + auto itr = GlobalMap.find(table->GetTableName()); + if (itr == GlobalMap.end()) { + (void)GlobalMap.insert( + pair>(table->GetTableName(), vector(maxAccessTypes, 0))); + } + GlobalMap.at(table->GetTableName())[access->GetType()]++; + auto test = GlobalMap.at(table->GetTableName()); + } + for (auto itr = GlobalMap.begin(); itr != GlobalMap.end(); (void)++itr) { + MOT_LOG_INFO("TABLE: %s", (itr->first).c_str()); + auto vec = itr->second; + for (uint8_t index = 0; index < vec.size(); index++) { + if (vec[index] > 0) { + MOT_LOG_INFO("%s = %d", enTxnStates[index], (uint32_t)vec[index]); + } + } + cout << "-------------\n"; + } +} + +void TxnAccess::PrintPoolStats() +{ + if (m_accessPool != nullptr) { + PoolStatsSt stats; + errno_t erc = memset_s(&stats, sizeof(PoolStatsSt), 0, sizeof(PoolStatsSt)); + securec_check(erc, "\0", "\0"); + stats.m_type = PoolStatsT::POOL_STATS_ALL; + + m_accessPool->GetStats(stats); + m_accessPool->PrintStats(stats, "Access Pool", LogLevel::LL_INFO); + } + m_dummyTable.PrintStats(); +} + +RC TxnAccess::AddColumn(Access* ac) +{ + RC rc = RC_OK; + uint8_t* bms = nullptr; + uint16_t newSize = 0; + if (ac->m_modifiedColumns.IsInitialized()) { + uint16_t oldSize = ac->m_modifiedColumns.GetSize(); + newSize = oldSize + 1; + int newSizeBytes = BitmapSet::GetLength(newSize); + int oldSizeBytes = BitmapSet::GetLength(oldSize); + if (newSizeBytes != oldSizeBytes) { + bms = m_dummyTable.CreateBitMapBuffer(newSize); + if (__builtin_expect(bms == nullptr, 0)) { + // if modified_columns allocation failed, release allocated row + rc = RC::RC_PANIC; + MOT_REPORT_ERROR(MOT_ERROR_OOM, "BitmapAddColumn", "Failed to create columns bitmap"); + } else { + uint8_t* oldBms = ac->m_modifiedColumns.GetData(); + errno_t erc = memcpy_s(bms, newSizeBytes, oldBms, oldSizeBytes); + securec_check(erc, "\0", "\0"); + ac->m_modifiedColumns.Init(bms, newSize); + m_dummyTable.DestroyBitMapBuffer(oldBms, oldSize); + } + } else { + ac->m_modifiedColumns.AddBit(); + } + } + + return rc; +} + +void TxnAccess::DropColumn(Access* ac, Column* col) +{ + if (ac->m_modifiedColumns.IsInitialized()) { + ac->m_modifiedColumns.UnsetBit(col->m_id - 1); + } +} + +RC TxnAccess::GeneratePrimaryIndexUpdate(Access* primaryAccess, TxnIxColUpdate* colUpd) +{ + RC rc = RC_OK; + Row* row = primaryAccess->GetTxnRow(); + MOT_ASSERT(row != nullptr); + MOT_ASSERT(false); + // insert new keys + for (uint16_t i = 1; i < colUpd->m_arrLen && rc == RC_OK; i++) { + if (colUpd->m_ix[i] != nullptr) { + InsItem* insItem = m_txnManager->GetNextInsertItem(); + if (insItem == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Update Index", "Cannot get insert item"); + return RC_MEMORY_ALLOCATION_ERROR; + } + insItem->SetItem(row, colUpd->m_ix[i]); + rc = m_txnManager->InsertRow(row); + } + } + return rc; +} + +RC TxnAccess::SecIdxUpdGenerateDeleteKeys(Access* primaryAccess, TxnIxColUpdate* colUpd) +{ + RC rc = RC_OK; + Access* current_access = nullptr; + Row* row = primaryAccess->GetTxnRow(); + MOT_ASSERT(row != nullptr); + Sentinel* outSentinel = nullptr; + // find references to old keys and create Access to remove it + for (uint16_t i = 1; i < colUpd->m_arrLen; i++) { + current_access = nullptr; + if (colUpd->m_ix[i] != nullptr) { + // delete by old key + rc = colUpd->m_tab->FindRowByIndexId( + colUpd->m_ix[i], colUpd->m_oldKeys[i], outSentinel, m_txnManager->GetThdId()); + if (rc != RC_OK) { + // row already deleted!! Abort + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + break; + } + auto it = m_rowsSet->find(outSentinel); + // Filter Rows + if (it != m_rowsSet->end()) { + Access* ac = (*it).second; + if (ac->m_type != INS) { + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + break; + } + if (ac->m_secondaryDelKey != nullptr) { + ac->m_origSentinel->GetIndex()->DestroyKey(ac->m_secondaryDelKey); + } + ac->m_secondaryDelKey = colUpd->m_oldKeys[i]; + // Use primary row for rollback + // In some cases we created an Index we cannot rely on access row + ac->m_localInsertRow = row; + m_txnManager->RollbackInsert(ac); + if (ac->m_params.IsUpgradeInsert() == false or ac->m_params.IsInsertOnDeletedRow() == true) { + (void)m_rowsSet->erase(it); + // need to perform index clean-up! + ReleaseAccess(ac); + } else { + // Transform INS to DEL + ac->m_type = DEL; + ac->m_params.UnsetUpgradeInsert(); + ac->m_localInsertRow = nullptr; + } + } else { + if (m_txnManager->GetIsolationLevel() > READ_COMMITED) { + PrimarySentinel* ps = outSentinel->GetVisiblePrimaryHeader(m_txnManager->GetVisibleCSN()); + if (ps == nullptr) { + // Sentinel is not visible for current transaction abort! + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + break; + } + } + // generate New Accesses! + current_access = GetNewSecondaryAccess(outSentinel, primaryAccess, rc); + if (current_access == nullptr) { + return rc; + } + + if (colUpd->m_ix[i]->GetUnique()) { + current_access->m_params.SetUniqueIndex(); + current_access->m_secondaryUniqueNode = + static_cast(outSentinel)->GetNodeByCSN(m_txnManager->GetVisibleCSN()); + if ((current_access->m_secondaryUniqueNode == nullptr) || + (current_access->m_secondaryUniqueNode->GetEndCSN() != Sentinel::SENTINEL_INIT_CSN)) { + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + break; + } + current_access->m_csn = current_access->m_secondaryUniqueNode->GetStartCSN(); + } + current_access->m_type = DEL; + current_access->m_secondaryDelKey = colUpd->m_oldKeys[i]; + current_access->m_params.SetIndexUpdate(); + auto ret = m_rowsSet->insert(RowAccessPair_t(outSentinel, current_access)); + MOT_ASSERT(ret.second == true); + } + // in case of error the key will be released by access destroy function + colUpd->m_oldKeys[i] = nullptr; + } + } + + // In case of error destroy the access + if (rc != RC_OK and current_access != nullptr) { + ReleaseAccess(current_access); + current_access = nullptr; + } + + return rc; +} + +RC TxnAccess::GenerateSecondaryIndexUpdate(Access* primaryAccess, TxnIxColUpdate* colUpd) +{ + Row* row = primaryAccess->GetTxnRow(); + MOT_ASSERT(row != nullptr); + Sentinel* outSentinel = nullptr; + + // find references to old keys and create Access to remove it + // insert new values + RC rc = SecIdxUpdGenerateDeleteKeys(primaryAccess, colUpd); + + // insert new keys + for (uint16_t i = 1; i < colUpd->m_arrLen && rc == RC_OK; i++) { + if (colUpd->m_ix[i] != nullptr) { + MOT_ASSERT(colUpd->m_newKeys[i] != nullptr); + (void)colUpd->m_tab->FindRowByIndexId( + colUpd->m_ix[i], colUpd->m_newKeys[i], outSentinel, m_txnManager->GetThdId()); + if (outSentinel != nullptr) { + // Check A->B->A problem for update column + auto it = m_rowsSet->find(outSentinel); + if (it != m_rowsSet->end()) { + Access* current_access = it->second; + if (current_access->m_type != DEL) { + rc = RC_UNIQUE_VIOLATION; + auto err = m_txnManager->GetNextInsertItem(); + err->SetItem(row, colUpd->m_ix[i]); + GetInsertMgr()->ReportError(rc, err); + continue; + } + // Detect dependency + if (primaryAccess->m_stmtCount > current_access->m_stmtCount) { + if (primaryAccess->m_redoStmt < current_access->m_redoStmt) { + // Error detected dependency + m_txnManager->m_err = RC_SERIALIZATION_FAILURE; + rc = RC_SERIALIZATION_FAILURE; + continue; + } + } + if (colUpd->m_ix[i]->GetIndexOrder() == IndexOrder::INDEX_ORDER_SECONDARY) { + MOT_ASSERT(current_access->m_type == DEL); + // Undo update column + (void)m_rowsSet->erase(it); + ReleaseAccess(current_access); + continue; + } + } + } + m_txnManager->GetNextInsertItem()->SetItem(row, colUpd->m_ix[i]); + rc = m_txnManager->InsertRow(row, colUpd->m_newKeys[i]); + if (rc == RC_OK) { + GetLastAccess()->m_params.SetIndexUpdate(); + GetLastAccess()->m_secondaryDelKey = colUpd->m_newKeys[i]; + colUpd->m_newKeys[i] = nullptr; + } + } + } + return rc; +} + +void TxnAccess::ClearTableCache() +{ + for (auto itr = m_tableStat.begin(); itr != m_tableStat.end();) { + if (itr->second >= FREE_THREAD_CACHE_THRESHOLD) { + Table* table = nullptr; + if (m_txnManager->IsRecoveryTxn()) { + table = GetTableManager()->GetTableSafeByExId(itr->first); + } else { + table = GetTableManager()->GetTableByExternal(itr->first); + } + if (table != nullptr) { + table->ClearRowCache(); + if (m_txnManager->IsRecoveryTxn()) { + table->Unlock(); + } + } + itr = m_tableStat.erase(itr); + } else { + (void)++itr; + } + } +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_access.h b/src/gausskernel/storage/mot/core/system/transaction/txn_access.h index 2fd37ad65..d4f400bf5 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_access.h +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_access.h @@ -27,7 +27,7 @@ #ifndef TXN_ACCESS_H #define TXN_ACCESS_H -#include +#include #include #include @@ -37,6 +37,7 @@ #include "sentinel.h" #include "utilities.h" #include "txn_local_allocators.h" +#include "txn_insert_action.h" #include "access_params.h" #include "access.h" #include "bitmapset.h" @@ -47,13 +48,10 @@ class Table; class Row; class TxnManager; -/** @var Transaction State Machine size. */ -constexpr uint8_t TSM_SIZE = 6; -/** @var default access size */ -constexpr uint32_t DEFAULT_ACCESS_SIZE = 500; - typedef std::pair RowAccessPair_t; +using S_SentinelNodePool = unordered_multimap; + typedef DummyIndex DummyIndexImpl; typedef std::map> TxnOrderedSet_t; @@ -64,7 +62,6 @@ typedef std::map> TxnOrderedSet_t; * */ class alignas(64) TxnAccess { - public: /** Constructor. */ TxnAccess(); @@ -91,10 +88,7 @@ public: /** * @brief Searches for a row in the local cache, and if found, promotes the * row state accordingly. - * @param table The table in which the row is to be found. - * @param type The new row state to update. - * @param key The pointer to the key buffer. - * @param keyLen The length of the key buffer. + * @param currentKey The key where the row is mapped to * @return The cached row or null pointer if the row is not cached. */ Access* RowLookup(void* const currentKey); @@ -108,6 +102,13 @@ public: */ RC AccessLookup(const AccessType type, Sentinel* const originalSentinel, Row*& r_local_Row); + /** + * @brief Searches for a row in the cache using the sentinel as a key + * @param sentinel The key for the cache + * @return + */ + RC CheckDuplicateInsert(Sentinel* const sentinel); + /** * @brief Stores row access entry in the local cache. * @param table The table to which the row belongs. @@ -117,6 +118,14 @@ public: */ Row* MapRowtoLocalTable(const AccessType type, Sentinel* const& originalSentinel, RC& rc); + /** + * @brief Fetch Row from PS when SS is Mapped. + * @param type The new row state to update. + * @param originalSentinel The global secondary sentinel + * @return The row as it is stored in the cache. + */ + Row* FetchRowFromPrimarySentinel(const AccessType type, Sentinel* const& originalSentinel, RC& rc); + /** * @brief Applies row state changes according to state machine rules. * @param type The new row access type. @@ -126,12 +135,30 @@ public: RC UpdateRowState(AccessType type, Access* ac); /** - * @brief Retrieves a row using the concurrency control mechanism. - * @param row The row to retrieve. - * @param type The type of access required. - * @return The row access entry. + * @brief Generate a new Access object for the current inserted sentinel + * @param sentinel The inserted sentinel. + * @param row the new row. + * @param isUpgrade is the insert mode == upgrade + * @return The new access entry. */ - Access* GetNewRowAccess(const Row* row, AccessType type, RC& rc); + Access* GetNewInsertAccess(Sentinel* const& sentinel, Row* row, RC& rc, bool isUpgrade); + + /** + * @brief Generate a new Access object for the current modified key + * @param sentinel The inserted sentinel. + * @param rc return value. + * @return The new access entry. + */ + Access* GetNewRowAccess(Sentinel* const& originalSentinel, AccessType type, RC& rc); + + /** + * @brief Generate a new Access object for the current secondary inserted sentinel + * @param sentinel The inserted sentinel. + * @param primary_access the primary access info. + * @param rc return value. + * @return The new access entry. + */ + Access* GetNewSecondaryAccess(Sentinel* const& originalSentinel, Access* primary_access, RC& rc); /** * @brief Retrieves the last row access entry that was accessed. @@ -142,34 +169,44 @@ public: return m_lastAcc; } + inline Row* GetLastAccessedDraft() + { + return m_lastAcc->GetLocalVersion(); + } + inline TxnOrderedSet_t& GetOrderedRowSet() { return *m_rowsSet; } + inline S_SentinelNodePool* GetSentinelObjectPool() + { + return m_sentinelObjectPool; + } + inline void IncreaseTableStat(Table* t) { - m_tableStat[t]++; + m_tableStat[t->GetTableExId()]++; } inline void RemoveTableFromStat(Table* t) { - m_tableStat.erase(t); + (void)m_tableStat.erase(t->GetTableExId()); + } + + inline uint32_t Size() const + { + return m_rowCnt; } /** * @brief Retrieves all the used tables and try to clean the cache if possible. */ - inline void ClearTableCache() + void ClearTableCache(); + + void ClearDummyTableCache() { - for (auto& table : m_tableStat) { - if (table.second > DEFAULT_ACCESS_SIZE) { - MOT_LOG_INFO( - "ClearTableStat: Table = %s items = %lu\n", table.first->GetTableName().c_str(), table.second); - table.first->ClearRowCache(); - } - } - m_tableStat.clear(); + m_dummyTable.ClearRowCache(); } /** @@ -181,11 +218,29 @@ public: Row* AddInsertToLocalAccess(Sentinel* org_sentinel, Row* org_row, RC& rc, bool isUpgrade = false); /** - * @brief For Read-Commited we return a copy of the current row + * @brief Get a visible version for the sentinel * @param sentinel The row-header - * @return row zero with current copy + * @param csn the txn snapshot + * @return Visible row version */ - Row* GetReadCommitedRow(Sentinel* sentinel); + Row* GetVisibleRowVersion(Sentinel* sentinel, uint64_t csn); + + /** + * @brief Get a primary sentinel from a secondary sentinel + * @param sentinel The row-header + * @param csn the txn snapshot + * @return Visible row version + */ + PrimarySentinel* GetVisiblePrimarySentinel(Sentinel* sentinel, uint64_t csn); + + /** + * @brief Get a visible row per operation type and isolation-level + * @param sentinel The primary sentinel + * @param the DML + * @param rc return code + * @return Visible row version + */ + Row* GetVisibleRow(PrimarySentinel* sentinel, AccessType type, RC& rc); /** * @brief Undo insert operation if possible after delete @@ -200,7 +255,31 @@ public: * @param element The current deleted row * @return RC */ - RC GenerateDeletes(Access* element); + RC GenerateSecondaryAccess(Access* primaryAccess, AccessType acType); + + /** + * @brief Wrapper to Generate draft version + * @param ac The new access object + * @param acType the operation + * @return RC + */ + RC GenerateDraft(Access* ac, AccessType acType); + + /** + * @brief Generate draft version and initialize access + * @param ac The new access object + * @param acType the operation + * @return RC + */ + RC CreateMVCCDraft(Access* ac, AccessType acType); + + /** + * @brief Generate missing objects for update on indexed column + * @return RC + */ + RC GenerateSecondaryIndexUpdate(Access* primaryAccess, TxnIxColUpdate* colUpd); + + RC GeneratePrimaryIndexUpdate(Access* primaryAccess, TxnIxColUpdate* colUpd); // Temporarily added since releaseAccess is private void PubReleaseAccess(Access* access) @@ -208,7 +287,7 @@ public: ReleaseAccess(access); } - TxnInsertAction* GetInsertMgr() const + TxnInsertAction* GetInsertMgr() { return m_insertManager; } @@ -218,7 +297,7 @@ public: * @param index The row access entry index. * @return The row access entry. */ - inline Access* GetAccessPtr(uint64_t index) const + inline Access* GetAccessPtr(uint64_t index) { if (index >= m_accessSetSize) { return nullptr; @@ -226,8 +305,70 @@ public: return m_accessesSetBuff[index]; } - /** @var The access set. */ - Access** m_accessesSetBuff = nullptr; + /** + * @brief Perform GC maintenance after every commit. + */ + void GcMaintenance(); + + // those functions are called on add/drop column in case + // we have updated rows in transaction and column nullbits has changed size + RC AddColumn(Access* ac); + void DropColumn(Access* ac, Column* col); + + /** @brief allocate new Access */ + Access* AllocNewAccess(uint32_t rowCnt) + { + Access* ac = m_accessPool->Alloc(rowCnt); + if (ac == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Create Access", "Failed to create new Access"); + } + return ac; + } + + /** @brief release Access */ + void ReleaseAccessToPool(Access* obj) + { + m_accessPool->Release(obj); + } + + /** @brief Rollback sub-transaction accesses */ + void RollbackSubTxn(uint32_t accessId) + { + return ReleaseSubTxnAccesses(accessId); + } + + void Print(); + + void PrintStats(); + + bool IsTxnAccessEmpty() + { + if (m_rowCnt == 0 and GetInsertMgr()->IsInsertSetEmpty()) { + return true; + } + return false; + } + + Row* GetRowZeroCopyIfAny(Row* localRow); + + void GetRowZeroCopy(Row* localRow); + + void PrintPoolStats(); + + /** @var Transaction State Machine size. */ + static constexpr uint8_t TSM_SIZE = 6; + + static const char* const enTxnStates[]; + +private: + /** @var default access size */ + static constexpr uint32_t DEFAULT_ACCESS_SIZE = 500; + + static constexpr uint32_t FREE_THREAD_CACHE_THRESHOLD = 1024; + + static constexpr uint32_t ACCESS_SET_EXTEND_FACTOR = 2; + + static constexpr uint32_t ACCESS_SET_PRINT_THRESHOLD = 100; /** @var The number of rows stored in the access set array. */ uint32_t m_rowCnt = 0; @@ -238,12 +379,15 @@ public: /** @var The initial access set array size. */ uint32_t m_accessSetSize = DEFAULT_ACCESS_SIZE; -private: - static constexpr uint32_t ACCESS_SET_EXTEND_FACTOR = 2; + /** @var The access set. */ + Access** m_accessesSetBuff = nullptr; /** @var The transaction for which this cache is maintained. */ TxnManager* m_txnManager = nullptr; + /** @var Access objects memory pool. */ + ObjAllocInterface* m_accessPool = nullptr; + /** @var */ TxnInsertAction* m_insertManager = nullptr; @@ -253,18 +397,19 @@ private: /** @var Maintains ordered Access pointers by row address */ TxnOrderedSet_t* m_rowsSet = nullptr; - /** @var row_zero Used for read-only txn in - * RC isolation level - * */ + /** @var row_zero Used for altered row (New-Column) */ Row* m_rowZero = nullptr; + /** @var sentinelObject pool for SU usage */ + S_SentinelNodePool* m_sentinelObjectPool = nullptr; + /** @var m_dummyTable Points to a dummy table with tuple size of MAX_TUPLE_SIZE * used for transaction row caching **/ DummyTable m_dummyTable; /** @var m_tableStat Used for table cache management */ - std::unordered_map m_tableStat; + std::unordered_map m_tableStat; /** * @brief Reset the row access entry stored at the specified index to nullptr. @@ -281,10 +426,9 @@ private: /** * @brief Creates a new row access entry for the specified table and caches * it. - * @param table The table for which a row access entry id to be created. * @return Boolean value denoting success or failure. */ - bool CreateNewAccess(Table* table, AccessType type); + bool CreateNewAccess(AccessType type); /** * @brief Installs a new access set array. @@ -319,6 +463,9 @@ private: DestroyAccess(ac); } + /** @brief Rollback RD-ONLY Accesses. */ + void ReleaseSubTxnAccesses(uint32_t accessId); + /** @brief Doubles the size of the access set. */ bool ReallocAccessSet(); @@ -328,8 +475,28 @@ private: /** @brief Release all objects to the memory pool */ void ClearAccessSet(); + RC InitBitMap(Access* ac, int fieldCount); + /** @var Manage safe initialization/destruction in case of failure. */ - enum InitPhase { Startup, AllocBuf, InitDummyTab, InitDummyInd, CreateRowZero, CreateRowSet, Done } m_initPhase; + enum InitPhase { + Startup, + AllocBuf, + InitDummyTab, + InitDummyInd, + CreateRowZero, + CreateRowSet, + CreateInsertSet, + CreateAccessPool, + Done + } m_initPhase; + + RC LookupFilterRow(Access* curr_acc, const AccessType type, bool isCommitted, Row*& r_local_Row); + + RC SecIdxUpdGenerateDeleteKeys(Access* primaryAccess, TxnIxColUpdate* colUpd); + + RC SecondaryAccessDeleteTxnRows(Row* row, Table* table); + + RC SecondaryAccessDeleteGlobalRows(Access* primaryAccess, Row* row, Table* table); DECLARE_CLASS_LOGGER(); }; diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.cpp b/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.cpp index 05aeefdc9..07442ee98 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.cpp @@ -24,6 +24,7 @@ #include "txn_ddl_access.h" #include "debug_utils.h" +#include "txn_table.h" namespace MOT { TxnDDLAccess::DDLAccess::DDLAccess() @@ -33,21 +34,11 @@ TxnDDLAccess::DDLAccess::DDLAccess() m_entry = nullptr; } -uint64_t TxnDDLAccess::DDLAccess::GetOid() -{ - return m_oid; -} - void* TxnDDLAccess::DDLAccess::GetEntry() { return m_entry; } -DDLAccessType TxnDDLAccess::DDLAccess::GetDDLAccessType() -{ - return m_type; -} - void TxnDDLAccess::DDLAccess::Set(uint64_t oid, DDLAccessType accessType, void* ddlEntry) { m_oid = oid; @@ -55,21 +46,17 @@ void TxnDDLAccess::DDLAccess::Set(uint64_t oid, DDLAccessType accessType, void* m_entry = ddlEntry; } +void TxnDDLAccess::DDLAccess::ResetEntry() +{ + m_entry = nullptr; +} + TxnDDLAccess::TxnDDLAccess(TxnManager* txn) : m_txn(txn), m_initialized(false), m_size(0), m_accessList() {} -RC TxnDDLAccess::Init() +void TxnDDLAccess::Init() { - if (m_initialized) - return RC_OK; - m_initialized = true; - return RC_OK; -} - -uint32_t TxnDDLAccess::Size() -{ - return m_size; } void TxnDDLAccess::Reset() @@ -79,13 +66,19 @@ void TxnDDLAccess::Reset() m_accessList[i] = nullptr; } + for (ExternalTableMap::iterator it = m_txnTableMap.begin(); it != m_txnTableMap.end(); (void)++it) { + TxnTable* t = it->second; + delete t; + } + m_txnTableMap.clear(); m_size = 0; } RC TxnDDLAccess::Add(TxnDDLAccess::DDLAccess* ddlAccess) { - if (m_size == MAX_DDL_ACCESS_SIZE) + if (m_size == MAX_DDL_ACCESS_SIZE) { return RC_TXN_EXCEEDS_MAX_DDLS; + } m_accessList[m_size++] = ddlAccess; return RC_OK; } @@ -115,23 +108,24 @@ TxnDDLAccess::~TxnDDLAccess() // assumption is that caller destroyed referenced objects already if (m_initialized) { Reset(); + m_initialized = false; } + m_txn = nullptr; } void TxnDDLAccess::EraseByOid(uint64_t oid) { - bool deleted = false; - for (int i = 0; i < m_size; i++) { + uint16_t i = 0; + while (i < m_size) { if (m_accessList[i]->GetOid() == oid) { - delete m_accessList[i]; - deleted = true; - } else if (deleted) { - // remove empty spaces created by entry removal; - m_accessList[i - 1] = m_accessList[i]; + EraseAt(i); + + // EraseAt will move the remaining entries forward to remove the hole created by entry removal, + // so we still have to continue from i (without advancing). + continue; } + ++i; } - if (deleted) - m_size--; } void TxnDDLAccess::EraseAt(uint16_t index) @@ -144,4 +138,57 @@ void TxnDDLAccess::EraseAt(uint16_t index) } m_size--; } + +void TxnDDLAccess::AddTxnTable(TxnTable* table) +{ + m_txnTableMap[table->GetTableExId()] = table; +} + +void TxnDDLAccess::DelTxnTable(TxnTable* table) +{ + ExternalTableMap::iterator it = m_txnTableMap.find(table->GetTableExId()); + if (it != m_txnTableMap.end()) { + TxnTable* tab = it->second; + (void)m_txnTableMap.erase(it); + delete tab; + } +} + +Table* TxnDDLAccess::GetTxnTable(uint64_t tabId) +{ + ExternalTableMap::iterator it = m_txnTableMap.find(tabId); + if (it != m_txnTableMap.end()) { + if (!it->second->IsDropped()) { + it->second->ApplyAddIndexFromOtherTxn(); + return it->second; + } + } + return nullptr; +} + +RC TxnDDLAccess::ValidateDDLChanges(TxnManager* txn) +{ + RC res = RC_OK; + for (ExternalTableMap::iterator it = m_txnTableMap.begin(); it != m_txnTableMap.end() && res == RC_OK; (void)++it) { + TxnTable* tab = (TxnTable*)it->second; + res = tab->ValidateDDLChanges(txn); + } + return res; +} + +void TxnDDLAccess::ApplyDDLChanges(TxnManager* txn) +{ + for (ExternalTableMap::iterator it = m_txnTableMap.begin(); it != m_txnTableMap.end(); (void)++it) { + TxnTable* tab = (TxnTable*)it->second; + tab->ApplyDDLChanges(txn); + } +} + +void TxnDDLAccess::RollbackDDLChanges(TxnManager* txn) +{ + for (ExternalTableMap::iterator it = m_txnTableMap.begin(); it != m_txnTableMap.end(); (void)++it) { + TxnTable* tab = (TxnTable*)it->second; + tab->RollbackDDLChanges(txn); + } +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.h b/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.h index b96deb5b6..e3d9c3d99 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.h +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_ddl_access.h @@ -22,11 +22,12 @@ * ------------------------------------------------------------------------- */ -#include "global.h" - #ifndef TXN_DDL_ACCESS_H #define TXN_DDL_ACCESS_H +#include "global.h" +#include + #define MAX_DDL_ACCESS_SIZE 100 namespace MOT { @@ -49,11 +50,21 @@ enum DDLAccessType : uint8_t { /** @var drop index. */ DDL_ACCESS_DROP_INDEX, + /** @var add column. */ + DDL_ACCESS_ADD_COLUMN, + + /** @var drop column. */ + DDL_ACCESS_DROP_COLUMN, + + /** @var rename column. */ + DDL_ACCESS_RENAME_COLUMN, }; // forward declaration class OccTransactionManager; class TxnManager; +class Table; +class TxnTable; /** * @class TxnDDLAccess @@ -76,24 +87,31 @@ public: DDLAccess(); /* @brief Constructor. */ inline DDLAccess(uint64_t oid, DDLAccessType accessType, void* ddlEntry) - { - Set(oid, accessType, ddlEntry); - } + : m_oid(oid), m_type(accessType), m_entry(ddlEntry) + {} ~DDLAccess() - {} + { + m_entry = nullptr; + } /** * @brief GetOid, returns the ddl change object id. * @return uint64_t object id. */ - uint64_t GetOid(); + inline uint64_t GetOid() const + { + return m_oid; + } /** * @brief GetDDLAccessType, returns the ddl change type. * @return DDLAccessType. */ - DDLAccessType GetDDLAccessType(); + inline DDLAccessType GetDDLAccessType() const + { + return m_type; + } /** * @brief GetEntry, returns the object entry. @@ -105,6 +123,8 @@ public: */ void Set(uint64_t oid, DDLAccessType accessType, void* ddlEntry); + void ResetEntry(); + private: /** @var DDL entry object id */ uint64_t m_oid; @@ -126,7 +146,7 @@ public: * @brief Init. Initialize DDLTxnAccess class, currently not doing too much * as there is nothing to allocate or initialize. */ - RC Init(); + void Init(); /** * @brief Adds a new ddlAccess to the list of transaction DDL changes. @@ -136,7 +156,15 @@ public: /** * @brief Returns the number of "in-flight" transaction DDL changes. */ - uint32_t Size(); + inline uint16_t Size() const + { + return m_size; + } + + inline bool HasEnoughSpace() const + { + return (m_size < MAX_DDL_ACCESS_SIZE); + } /** * @brief Returns DDLAccess in the provided index. @@ -163,6 +191,13 @@ public: */ void EraseByOid(uint64_t oid); + void AddTxnTable(TxnTable* table); + void DelTxnTable(TxnTable* table); + Table* GetTxnTable(uint64_t tabId); + RC ValidateDDLChanges(TxnManager* txn); + void ApplyDDLChanges(TxnManager* txn); + void RollbackDDLChanges(TxnManager* txn); + private: /** @var TxnManager the Transaction owning the ddl changes */ TxnManager* m_txn; @@ -175,6 +210,9 @@ private: /** @var Denotes an array of DDLAccess entries */ TxnDDLAccess::DDLAccess* m_accessList[MAX_DDL_ACCESS_SIZE]; + + using ExternalTableMap = std::map; + ExternalTableMap m_txnTableMap; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_insert_action.h b/src/gausskernel/storage/mot/core/system/transaction/txn_insert_action.h index 51e853965..62c014752 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_insert_action.h +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_insert_action.h @@ -27,7 +27,7 @@ #ifndef TXN_INSERT_ACTION_H #define TXN_INSERT_ACTION_H -#include +#include namespace MOT { /** @@ -37,7 +37,7 @@ namespace MOT { class InsItem { public: /** @brief Default constructor. */ - InsItem() : m_index(nullptr), m_row(nullptr), m_key(nullptr), m_indexOrder(IndexOrder::INDEX_ORDER_PRIMARY) + InsItem() : m_index(nullptr), m_row(nullptr) {} /** @@ -52,19 +52,15 @@ public: * @brief Destructor. */ inline ~InsItem() - {} + { + m_row = nullptr; + m_index = nullptr; + } - inline __attribute__((always_inline)) void SetItem(Row* row, Index* index, Key* key) + inline __attribute__((always_inline)) void SetItem(Row* row, Index* index) { m_row = row; m_index = index; - m_indexOrder = index->GetIndexOrder(); - m_key = key; - } - - inline Key* GetKey() const - { - return m_key; } /** @@ -73,7 +69,7 @@ public: */ inline IndexOrder getIndexOrder() const { - return m_indexOrder; + return m_index->GetIndexOrder(); } /** @var The index to insert the row into. */ @@ -82,12 +78,6 @@ public: /** @var The new row to insert. */ Row* m_row; - /** @var The key by which to insert the row. */ - MOT::Key* m_key; - - /** @var The order of the stored index. */ - IndexOrder m_indexOrder; - private: DECLARE_CLASS_LOGGER() }; @@ -125,49 +115,69 @@ public: /** * @brief Executes all stored row insertion requests. * @param row + * @param isUpdateColumn Indicate whether the insert source is update column * @return Return code denoting the execution result. */ - RC ExecuteOptimisticInsert(Row* row); + RC ExecuteOptimisticInsert(Row* row, Key* updateColumnKey = nullptr); + + /** + * @brief Executes all stored row insertion requests in recovery. + * @param row + * @return Return code denoting the execution result. + */ + RC ExecuteRecoveryOCCInsert(Row* row); /** * @bruef Retrieves the first insertion request. * @return The first insertion request. */ - inline InsItem* BeginCursor() const + inline InsItem* BeginCursor() { return &(m_insertSet[0]); }; - inline InsItem* EndCursor() const + inline InsItem* EndCursor() { return (&m_insertSet[m_insertSetSize]); }; void ReportError(RC rc, InsItem* currentItem = nullptr); + uint32_t GetInsertSetSize() const + { + return m_insertSetSize; + } + + bool IsInsertSetEmpty() const + { + return (m_insertSetSize == 0); + } + private: static constexpr uint32_t INSERT_ARRAY_EXTEND_FACTOR = 2; /** @var The row insertion request array. */ InsItem* m_insertSet = nullptr; + /** @var The owning transaction manager object. */ + TxnManager* m_manager = nullptr; + /** @var Number of stored row insertion requests. */ uint32_t m_insertSetSize = 0; /** @var The capacity of the row insertion request array. */ uint32_t m_insertArraySize = 0; - /** @var The owning transaction manager object. */ - TxnManager* m_manager = nullptr; - /** @var The row insertion request array growth factor. */ static constexpr uint64_t INSERT_ARRAY_DEFAULT_SIZE = 64; inline InsItem* GetInsertItem(Index* index = nullptr) { - if (__builtin_expect(m_insertSetSize == m_insertArraySize, 0)) { - ReallocInsertSet(); + if (!ReallocInsertSet()) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Transaction Processing", "Cannot get insert item"); + return nullptr; + } } return &(m_insertSet[m_insertSetSize++]); @@ -180,12 +190,18 @@ private: void ShrinkInsertSet(); + RC AddInsertToLocalAccess(Row* row, InsItem* currentItem, Sentinel* pIndexInsertResult, bool& isMappedToCache); + /** * @brief Cleans up the current aborted row. */ void CleanupOptimisticInsert( InsItem* currentItem, Sentinel* pIndexInsertResult, bool isInserted, bool isMappedToCache); + void CleanupRecoveryOCCInsert(Row* row, std::vector& sentinels); + + void CleanupInsertReclaimKey(Row* row, Sentinel* sentinel); + // class non-copy-able, non-assignable, non-movable /** @cond EXCLUDE_DOC */ TxnInsertAction(const TxnInsertAction&) = delete; diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn_local_allocators.h b/src/gausskernel/storage/mot/core/system/transaction/txn_local_allocators.h index 851bda4dd..389b93135 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn_local_allocators.h +++ b/src/gausskernel/storage/mot/core/system/transaction/txn_local_allocators.h @@ -27,7 +27,7 @@ #ifndef TXN_LOCAL_ALLOCATORS_H #define TXN_LOCAL_ALLOCATORS_H -#include +#include #include #include "table.h" #include "row.h" @@ -36,15 +36,21 @@ #include "access.h" namespace MOT { + /** * @class DummyTable * @brief local row allocator for current transaction manager */ class DummyTable { public: - static constexpr double BITMAP_BITS_IN_BYTE = 8.0; + static constexpr uint32_t BITMAP_BITS_IN_BYTE = 8; - DummyTable() : m_slab(nullptr) + /* Max supported by the envelope (MaxHeapAttributeNumber in htup.h) */ + static constexpr uint32_t MAX_COLUMNS_PER_TABLE = 1600; + + static constexpr uint32_t MAX_BITMAP_BUFFER_SIZE = MAX_COLUMNS_PER_TABLE / BITMAP_BITS_IN_BYTE; + + DummyTable() : m_slab(nullptr), m_recovery(false) {} ~DummyTable() @@ -58,9 +64,11 @@ public: /** * @brief Initialize the slab allocator for sizes ranging from 64B to size of 32K */ - bool inline Init() + bool inline Init(bool recovery, bool global = false) { - m_slab = new (std::nothrow) SlabAllocator(SLAB_MIN_BIN, std::ceil(std::log2(MAX_TUPLE_SIZE) + 1), true); + m_recovery = recovery; + m_slab = new (std::nothrow) SlabAllocator( + SlabAllocator::SLAB_MIN_BIN, static_cast(std::ceil(std::log2(MAX_BITMAP_BUFFER_SIZE) + 1)), !global); if (m_slab == nullptr) { return false; } @@ -70,7 +78,7 @@ public: /** @brief return bitmap buffer */ uint8_t* CreateBitMapBuffer(int fieldCount) { - int size = std::ceil(fieldCount / BITMAP_BITS_IN_BYTE); + int size = BitmapSet::GetLength(fieldCount); void* buf = m_slab->Alloc(size); if (buf == nullptr) { return nullptr; @@ -80,53 +88,40 @@ public: return reinterpret_cast(buf); } - /** @brief return row match the current table schema */ - Row* CreateNewRow(Table* t, Access* ac) + void* AllocBuf(size_t size) { - int size = t->GetTupleSize() + sizeof(Row); - ac->m_localRowSize = size; - void* buf = m_slab->Alloc(size); - if (buf == nullptr) { - return nullptr; - } - return new (buf) Row(t); + int temp = size + sizeof(Row); + return m_slab->Alloc(temp); } - Row* CreateMaxRow(Table* t = nullptr) + void DestroyBuf(void* ptr, size_t size) { - int size = MAX_TUPLE_SIZE + sizeof(Row); - void* buf = m_slab->Alloc(size); - if (buf == nullptr) { - return nullptr; - } - return new (buf) Row(t); - } - - void DestroyRow(Row* row, Access* ac) - { - m_slab->Release(row, ac->m_localRowSize); + m_slab->Release(ptr, size); } + /** @brief Destroy Bitmap Buffer */ void DestroyBitMapBuffer(void* buffer, int fieldCount) { - int sizeInBytes = std::ceil(fieldCount / BITMAP_BITS_IN_BYTE); + int sizeInBytes = BitmapSet::GetLength(fieldCount); m_slab->Release(buffer, sizeInBytes); } - void DestroyMaxRow(Row* row) - { - int size = MAX_TUPLE_SIZE + sizeof(Row); - m_slab->Release(row, size); - } - + /** @brief Clear row cache */ void ClearRowCache() { m_slab->ClearFreeCache(); } + void PrintStats() + { + uint64_t gross = 0; + uint64_t netTotal = 0; + m_slab->PrintSize(gross, netTotal, "DummyTable Slab"); + } + private: - static constexpr int SLAB_MIN_BIN = 3; // 2^3 = 8Bytes SlabAllocator* m_slab; + bool m_recovery; }; /** @@ -147,11 +142,12 @@ public: } /** - * @brief Initialiaze the local slabAllocator + * @brief Initialize the local slabAllocator */ - bool inline Init() + bool inline Init(bool global = false) { - m_slab = new (std::nothrow) SlabAllocator(SLAB_MIN_BIN, std::ceil(std::log2(MAX_KEY_SIZE) + 1), true); + m_slab = new (std::nothrow) SlabAllocator( + SlabAllocator::SLAB_MIN_BIN, static_cast(std::ceil(std::log2(MAX_KEY_SIZE) + 1)), !global); if (m_slab == nullptr) { return false; } @@ -190,9 +186,14 @@ public: m_slab->ClearFreeCache(); } -private: - static constexpr int SLAB_MIN_BIN = 3; // 2^3 = 8Bytes + void PrintStats() + { + uint64_t gross = 0; + uint64_t netTotal = 0; + m_slab->PrintSize(gross, netTotal, "DummyIndex Slab"); + } +private: SlabAllocator* m_slab; DECLARE_CLASS_LOGGER(); diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.cpp new file mode 100644 index 000000000..bc3b83cdb --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.cpp @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * base_txn_logger.cpp + * Base class for transaction logging and serialization. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mot_engine.h" +#include "row.h" +#include "txn_table.h" +#include "redo_log_writer.h" +#include "base_txn_logger.h" + +namespace MOT { +DECLARE_LOGGER(RedoLog, BaseTxnLogger); + +BaseTxnLogger::BaseTxnLogger() : m_redoBuffer(nullptr), m_configuration(GetGlobalConfiguration()), m_txn(nullptr) +{} + +BaseTxnLogger::~BaseTxnLogger() +{ + // m_redoBuffer should be freed by derived class + m_redoBuffer = nullptr; + // m_txn should be freed by derived class + m_txn = nullptr; +} + +RC BaseTxnLogger::InsertRow(Access* ac, uint64_t transaction_id) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + MaxKey key; + Row* row = ac->GetTxnRow(); + MOT::Index* index = row->GetTable()->GetPrimaryIndex(); + key.InitKey(index->GetKeyLength()); + index->BuildKey(row->GetTable(), row, &key); + uint64_t tableId = row->GetTable()->GetTableId(); + uint64_t exId = row->GetTable()->GetTableExId(); + bool success = RedoLogWriter::AppendCreateRow( + *m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId, row->GetRowId()); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendCreateRow( + *m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId, row->GetRowId()); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::UpdateRow(Access* ac, BitmapSet& modifiedColumns, bool idxUpd) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + Row* row = ac->GetTxnRow(); + uint64_t previous_version = row->GetPrimarySentinel()->GetTransactionId(); + bool success = RedoLogWriter::AppendUpdate(*m_redoBuffer, row, &modifiedColumns, previous_version, idxUpd); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendUpdate(*m_redoBuffer, row, &modifiedColumns, previous_version, idxUpd); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::DeleteRow(Access* ac) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + MaxKey key; + Row* row = ac->GetGlobalVersion(); + MOT::Index* index = row->GetTable()->GetPrimaryIndex(); + key.InitKey(index->GetKeyLength()); + index->BuildKey(row->GetTable(), row, &key); + uint64_t tableId = row->GetTable()->GetTableId(); + uint64_t previous_version = row->GetPrimarySentinel()->GetTransactionId(); + uint64_t exId = row->GetTable()->GetTableExId(); + bool success = RedoLogWriter::AppendRemove(*m_redoBuffer, tableId, &key, previous_version, exId); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendRemove(*m_redoBuffer, tableId, &key, previous_version, exId); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::CreateTable(Table* table) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendTable(*m_redoBuffer, table); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendTable(*m_redoBuffer, table); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::DropTable(Table* table) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendDropTable(*m_redoBuffer, table); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendDropTable(*m_redoBuffer, table); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::CreateIndex(MOT::Index* index) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendIndex(*m_redoBuffer, index->GetTable(), index); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendIndex(*m_redoBuffer, index->GetTable(), index); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::DropIndex(MOT::Index* index) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + // No need to serialize delete of primary index. Delete of primary index can + // only happen in case of drop table. A drop table operation will follow the + // drop primary index operation so can disregard the drop primary index. + if (index->IsPrimaryKey()) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendDropIndex(*m_redoBuffer, index->GetTable(), index); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendDropIndex(*m_redoBuffer, index->GetTable(), index); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::TruncateTable(Table* table) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendTruncateTable(*m_redoBuffer, table); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendTruncateTable(*m_redoBuffer, table); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::AlterTableAddColumn(Table* table, Column* col) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendAlterTableAddColumn(*m_redoBuffer, table, col); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendAlterTableAddColumn(*m_redoBuffer, table, col); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::AlterTableDropColumn(Table* table, Column* col) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendAlterTableDropColumn(*m_redoBuffer, table, col); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendAlterTableDropColumn(*m_redoBuffer, table, col); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +RC BaseTxnLogger::AlterTableRenameColumn(Table* table, DDLAlterTableRenameColumn* alter) +{ + if (!m_configuration.m_enableRedoLog) { + return RC_OK; + } + + bool success = RedoLogWriter::AppendAlterTableRenameColumn(*m_redoBuffer, table, alter); + if (!success) { + WritePartial(); + success = RedoLogWriter::AppendAlterTableRenameColumn(*m_redoBuffer, table, alter); + } + return (success == true) ? RC_OK : RC_ERROR; +} + +void BaseTxnLogger::WritePartial() +{ + RedoLogWriter::AppendPartial(*m_redoBuffer, m_txn); + WriteToLog(); +} + +RC BaseTxnLogger::SerializeDropIndex(TxnDDLAccess::DDLAccess* ddlAccess, bool hasDML, IdxDDLAccessMap& idxDDLMap) +{ + RC status = RC_ERROR; + MOT::Index* index = (MOT::Index*)ddlAccess->GetEntry(); + if (index->IsPrimaryKey()) { + status = DropIndex(index); + } else { + IdxDDLAccessMap::iterator it = idxDDLMap.find(index->GetExtId()); + if (it != idxDDLMap.end()) { + // we create and drop no need for both them + MOT_LOG_DEBUG("Erase create index: %s %lu", index->GetName().c_str(), index->GetExtId()); + (void)idxDDLMap.erase(it); + status = RC_OK; + } else { + idxDDLMap[index->GetExtId()] = ddlAccess; + status = DropIndex(index); + } + } + return status; +} + +RC BaseTxnLogger::SerializeDDLs(IdxDDLAccessMap& idxDDLMap) +{ + MOT::Index* index = nullptr; + bool hasDML = (m_txn->m_accessMgr->Size() > 0 && !m_txn->m_isLightSession); + TxnDDLAccess* transactionDDLAccess = m_txn->m_txnDdlAccess; + if (transactionDDLAccess != nullptr && transactionDDLAccess->Size() > 0) { + RC status = RC_ERROR; + for (uint16_t i = 0; i < transactionDDLAccess->Size(); i++) { + TxnDDLAccess::DDLAccess* ddlAccess = transactionDDLAccess->Get(i); + if (ddlAccess == nullptr) { + return RC_ERROR; + } + DDLAccessType accessType = ddlAccess->GetDDLAccessType(); + switch (accessType) { + case DDL_ACCESS_CREATE_TABLE: + status = CreateTable((Table*)ddlAccess->GetEntry()); + break; + + case DDL_ACCESS_DROP_TABLE: + status = DropTable((Table*)ddlAccess->GetEntry()); + break; + + case DDL_ACCESS_CREATE_INDEX: + index = (MOT::Index*)ddlAccess->GetEntry(); + if (index->IsPrimaryKey()) { + status = CreateIndex(index); + } else { + MOT_LOG_DEBUG("Defer create index: %s %lu", index->GetName().c_str(), index->GetExtId()); + idxDDLMap[index->GetExtId()] = ddlAccess; + status = RC_OK; + } + break; + + case DDL_ACCESS_DROP_INDEX: + status = SerializeDropIndex(ddlAccess, hasDML, idxDDLMap); + break; + case DDL_ACCESS_TRUNCATE_TABLE: + status = TruncateTable((Table*)ddlAccess->GetEntry()); + break; + case DDL_ACCESS_ADD_COLUMN: { + DDLAlterTableAddDropColumn* alterAdd = (DDLAlterTableAddDropColumn*)ddlAccess->GetEntry(); + if (!alterAdd->m_table->GetIsNew()) { + status = AlterTableAddColumn(alterAdd->m_table, alterAdd->m_column); + } + break; + } + case DDL_ACCESS_DROP_COLUMN: { + DDLAlterTableAddDropColumn* alterDrop = (DDLAlterTableAddDropColumn*)ddlAccess->GetEntry(); + if (!alterDrop->m_table->GetIsNew()) { + status = AlterTableDropColumn(alterDrop->m_table, alterDrop->m_column); + } + break; + } + case DDL_ACCESS_RENAME_COLUMN: { + DDLAlterTableRenameColumn* alterRename = (DDLAlterTableRenameColumn*)ddlAccess->GetEntry(); + if (!alterRename->m_table->GetIsNew()) { + status = AlterTableRenameColumn(alterRename->m_table, alterRename); + } + break; + } + default: + return RC_ERROR; + } + if (status != RC_OK) { + MOT_LOG_ERROR("Serialize DDL finished with error: %d", status); + return RC_ERROR; + } + } + } + + return RC_OK; +} + +RC BaseTxnLogger::SerializeDMLs() +{ + RC status = RC_OK; + std::multimap orderedLog; + TxnOrderedSet_t& orderedSet = m_txn->m_accessMgr->GetOrderedRowSet(); + for (const auto& raPair : orderedSet) { + Access* access = raPair.second; + if (access->m_params.IsPrimarySentinel()) { + (void)orderedLog.insert(std::pair(access->m_redoStmt, access)); + } + } + for (const auto& raPair : orderedLog) { + Access* access = raPair.second; + status = SerializeDMLObject(access); + if (status != RC_OK) { + MOT_LOG_ERROR("Serialize DML finished with error: %d", status); + return status; + } + } + + return RC_OK; +} + +RC BaseTxnLogger::SerializeDMLObject(Access* access) +{ + RC status = RC_OK; + if (access != nullptr) { + switch (access->m_type) { + case INS: + if (access->m_params.IsPrimarySentinel()) { + if (access->m_params.IsUpgradeInsert() == true) { + // Check if we insert on a tombstone or deleted internally + if (access->m_params.IsInsertOnDeletedRow() == false) { + status = DeleteRow(access); + if (status != RC_OK) { + return status; + } + } + } + status = InsertRow(access, m_txn->GetInternalTransactionId()); + } + break; + case DEL: + if (access->m_params.IsPrimarySentinel()) { + status = DeleteRow(access); + } + break; + case WR: + if (access->m_params.IsPrimarySentinel()) { + status = UpdateRow(access, access->m_modifiedColumns, access->m_params.IsIndexUpdate()); + } + break; + default: + break; + } + } + + return status; +} + +RC BaseTxnLogger::SerializeTransaction() +{ + MOT_LOG_TRACE("BaseTransactionLogger::SerializeTransaction - Serializing TXN [%lu:%lu]", + m_txn->GetInternalTransactionId(), + m_txn->GetTransactionId()); + + IdxDDLAccessMap idxDDLMap; + RC status = SerializeDDLs(idxDDLMap); + if (status != RC_OK) { + MOT_LOG_ERROR("Failed to serialize DDLs: %d", status); + return status; + } + + if (m_txn->m_isLightSession) { + MOT_LOG_DEBUG("Serialize DDL light session finished"); + return RC_OK; + } + + status = SerializeDMLs(); + if (status != RC_OK) { + MOT_LOG_ERROR("Failed to serialize DMLs: %d", status); + return status; + } + + // create operations for unique indexes + IdxDDLAccessMap::iterator it = idxDDLMap.begin(); + while (it != idxDDLMap.end()) { + DDLAccessType accessType = it->second->GetDDLAccessType(); + switch (accessType) { + case DDL_ACCESS_CREATE_INDEX: { + MOT::Index* index = (MOT::Index*)it->second->GetEntry(); + MOT_LOG_DEBUG("Send create index: %s %lu", index->GetName().c_str(), index->GetExtId()); + status = CreateIndex(index); + break; + } + default: + break; + } + + if (status != RC_OK) { + return RC_ERROR; + } + + (void)++it; + } + idxDDLMap.clear(); + return RC_OK; +} +} // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.h b/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.h new file mode 100644 index 000000000..85de6c1c6 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * base_txn_logger.h + * Base class for transaction logging and serialization. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction_logger/base_txn_logger.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef BASE_TXN_LOGGER_H +#define BASE_TXN_LOGGER_H + +#include "redo_log_buffer.h" +#include "bitmapset.h" +#include "txn_ddl_access.h" + +namespace MOT { +class MOTConfiguration; +class TxnManager; +class Table; +class Row; +class Index; +class DDLAlterTableRenameColumn; +class Column; +class Access; +/** + * @class BaseTxnLogger + * @brief Base class for transaction logging and serialization + */ +class BaseTxnLogger { +public: + BaseTxnLogger(); + BaseTxnLogger(const BaseTxnLogger& orig) = delete; + BaseTxnLogger& operator=(const BaseTxnLogger& orig) = delete; + virtual ~BaseTxnLogger(); + + /** + * @brief Appends a row into the redo log buffer + * @param row the row to insert + * @return The status of the operation. + */ + RC InsertRow(Access* ac, uint64_t transaction_id); + + /** + * @brief Delta updates a row + * @param row The updated row + * @param modifiedColumns The delta columns + * @return The status of the operation. + */ + RC UpdateRow(Access* ac, BitmapSet& modifiedColumns, bool idxUpd); + + /** + * @brief Inserts a delete row to the redo log buffer + * @param row The row to delete + * @return The status of the operation. + */ + RC DeleteRow(Access* ac); + + /** + * @brief Inserts a table's creation data to the redo log buffer + * @param table The table to add. + * @return The status of the operation. + */ + RC CreateTable(Table* table); + + /** + * @brief Inserts a delete table op into the redo log buffer + * @param table the table to delete. + * @return RC value that indicates the status of the operation. + */ + RC DropTable(Table* table); + + /** + * @brief Inserts a create index op into the redo log buffer + * @param index The index to create. + * @return The status of the operation. + */ + RC CreateIndex(Index* index); + + /** + * @brief inserts a drop index op into the redo log buffer + * @param index the index to delete. + * @return RC value that indicates the status of the operation. + */ + RC DropIndex(Index* index); + + /** + * @brief inserts an add column op into the redo log buffer + * @param table the table that the added column belongs to. + * @param col added column. + * @return RC value that indicates the status of the operation. + */ + RC AlterTableAddColumn(Table* table, Column* col); + + /** + * @brief inserts a drop column op into the redo log buffer + * @param table the table that the dropped column belongs to. + * @param col dropped column. + * @return RC value that indicates the status of the operation. + */ + RC AlterTableDropColumn(Table* table, Column* col); + + /** + * @brief inserts a table rename op into the redo log buffer + * @param table the table that the column belongs to. + * @param alter the renamed column. + * @return RC value that indicates the status of the operation. + */ + RC AlterTableRenameColumn(Table* table, DDLAlterTableRenameColumn* alter); + + /** + * @brief inserts a truncate table op into the redo log buffer + * @param table the table to truncate. + * @return RC value that indicates the status of the operation. + */ + RC TruncateTable(Table* table); + + /** + * @brief Writes the whole transaction's DDLs and DMLs into the redo buffer + * @return The status of the operation. + */ + RC SerializeTransaction(); + + /** + * @brief Writes buffer data to the logger + */ + virtual void WriteToLog() = 0; + + inline void SetTxn(TxnManager* txn) + { + m_txn = txn; + } + + inline RedoLogBuffer& GetBuffer() + { + return *m_redoBuffer; + } + +protected: + /* Map of Index ID to DDLAccess. */ + using IdxDDLAccessMap = std::map; + /** + * @brief When the buffer is full, flush its contents to the log + */ + void WritePartial(); + + /** + * @brief Writes the whole transaction's DDLs into the redo buffer + * @param[out] idxDDLMap Map of Index ID to DDLAccess. + * @return The status of the operation. + */ + RC SerializeDDLs(IdxDDLAccessMap& idxDDLMap); + + RC SerializeDropIndex(TxnDDLAccess::DDLAccess* ddlAccess, bool hasDML, IdxDDLAccessMap& idxDDLMap); + + /** + * @brief Writes the whole transaction's DMLs into the redo buffer + * @return The status of the operation. + */ + RC SerializeDMLs(); + + RC SerializeDMLObject(Access* access); + /* Member variables */ + RedoLogBuffer* m_redoBuffer; + MOTConfiguration& m_configuration; + TxnManager* m_txn; +}; +} // namespace MOT + +#endif /* BASE_TXN_LOGGER_H */ diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.cpp index 6c05e6204..1ef414049 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.cpp @@ -26,7 +26,7 @@ #include "commit_group.h" #include "utilities.h" -#include "group_synchronous_redo_log_handler.h" +#include "group_sync_redo_log_handler.h" namespace MOT { DECLARE_LOGGER(CommitGroup, RedoLog); @@ -62,7 +62,7 @@ int CommitGroup::AddToGroup(RedoLogBuffer* buffer) val = (m_groupSize.fetch_add(1)); if (val >= (int)m_maxGroupCommitSize) { // group is full... undo our add - m_groupSize.fetch_sub(1); + (void)m_groupSize.fetch_sub(1); m_rwlock.RdUnlock(); return -1; } @@ -76,10 +76,10 @@ int CommitGroup::AddToGroup(RedoLogBuffer* buffer) void CommitGroup::LogGroup() { ILogger* logger = m_handler->GetLogger(); - logger->AddToLog(m_groupData, m_groupSize); + (void)logger->AddToLog(m_groupData, m_groupSize); logger->FlushLog(); m_commited = true; - MOT_LOG_DEBUG("group committed. num entries: %d, handler id: %d", m_groupSize, m_handlerId); + MOT_LOG_DEBUG("group committed. num entries: %d, handler id: %d", m_groupSize.load(), m_handlerId); } void CommitGroup::Commit(bool isLeader, std::shared_ptr groupRef) @@ -94,9 +94,9 @@ void CommitGroup::Commit(bool isLeader, std::shared_ptr groupRef) void CommitGroup::WaitLeader(std::shared_ptr groupRef) { - m_numWaiters.fetch_add(1); + (void)m_numWaiters.fetch_add(1); std::unique_lock lock(m_fullGroupMutex); - m_fullGroupCV.wait_for(lock, m_groupTimeout, [this] { return m_groupSize >= m_maxGroupCommitSize; }); + (void)m_fullGroupCV.wait_for(lock, m_groupTimeout, [this] { return m_groupSize >= m_maxGroupCommitSize; }); m_rwlock.WrLock(); m_closed = true; m_handler->CloseGroup(groupRef); @@ -110,9 +110,9 @@ void CommitGroup::WaitMember() if (val == m_maxGroupCommitSize - 1) { // i was the last one, all group txns are waiting MOT_LOG_DEBUG("all in, releasing group to commit. group size: %d, " "handler id: %d", - m_groupSize, + m_groupSize.load(), m_handlerId); - std::lock_guard lock(m_fullGroupMutex); + std::lock_guard groupLock(m_fullGroupMutex); m_fullGroupCV.notify_all(); } m_groupCommitedCV.wait(lock, [this] { return m_commited; }); diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.h b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.h index 00f82a466..9bb587954 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/commit_group.h @@ -54,7 +54,7 @@ public: * or -1 in case the group could not be joined */ int AddToGroup(RedoLogBuffer* buffer); - inline uint32_t GetGroupSize() + inline uint32_t GetGroupSize() const { return m_groupSize; } @@ -66,13 +66,13 @@ public: /** * @brief On a leader, waits for the right condition to commit - * as a member waits for the group to be comitted + * as a member waits for the group to be committed * @param isLeader is the current caller a leader * @param groupRef a reference to the group */ void Commit(bool isLeader, std::shared_ptr groupRef); - inline bool IsCommitted() + inline bool IsCommitted() const { return m_commited; } diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.cpp similarity index 95% rename from src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.cpp rename to src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.cpp index 65f66e2fb..133f6addd 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.cpp @@ -13,17 +13,17 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * group_synchronous_redo_log_handler.cpp + * group_synch_redo_log_handler.cpp * Implements a group commit redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * group_synchronous_redo_log/group_synchronous_redo_log_handler.cpp + * group_synchronous_redo_log/group_synch_redo_log_handler.cpp * * ------------------------------------------------------------------------- */ -#include "group_synchronous_redo_log_handler.h" +#include "group_sync_redo_log_handler.h" #include "utilities.h" #include "mot_configuration.h" @@ -64,7 +64,7 @@ void GroupSyncRedoLogHandler::DestroyBuffer(RedoLogBuffer* buffer) void GroupSyncRedoLogHandler::CloseGroup(std::shared_ptr group) { std::shared_ptr nullGroup(nullptr); - std::atomic_compare_exchange_weak(&m_currentGroup, &group, nullGroup); + (void)std::atomic_compare_exchange_weak(&m_currentGroup, &group, nullGroup); uint64_t curGroupCommitSize = GetGlobalConfiguration().m_groupCommitSize; std::chrono::microseconds curGroupCommitTimeoutUSec = std::chrono::microseconds(GetGlobalConfiguration().m_groupCommitTimeoutUSec); @@ -87,7 +87,6 @@ RedoLogBuffer* GroupSyncRedoLogHandler::WriteToLog(RedoLogBuffer* buffer) std::shared_ptr myGroup = make_shared(buffer, this, m_id); bool joined = false; bool leader = false; - bool tail = false; bool retryLeadership = false; int groupIndex = 0; const int maxNullLoops = 10000; @@ -147,7 +146,6 @@ RedoLogBuffer* GroupSyncRedoLogHandler::WriteToLog(RedoLogBuffer* buffer) // The group will be closed anyhow by the group leader but for // optimization, maybe we can close the group earlier (HERE) MOT_LOG_DEBUG("I am the last on the group. closing group"); - tail = true; CloseGroup(joinedGroup); } } diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.h b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.h similarity index 89% rename from src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.h rename to src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.h index c931eef82..fe6a2e0ca 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_synchronous_redo_log_handler.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/group_sync_redo_log_handler.h @@ -13,18 +13,18 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * group_synchronous_redo_log_handler.h + * group_sync_redo_log_handler.h * Implements a group commit redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * group_synchronous_redo_log/group_synchronous_redo_log_handler.h + * group_synchronous_redo_log/group_sync_redo_log_handler.h * * ------------------------------------------------------------------------- */ -#ifndef GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H -#define GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H +#ifndef GROUP_SYNC_REDO_LOG_HANDLER_H +#define GROUP_SYNC_REDO_LOG_HANDLER_H #include #include @@ -71,7 +71,7 @@ public: */ void CloseGroup(std::shared_ptr group); - inline uint64_t GetGroupCommitSize() + inline uint64_t GetGroupCommitSize() const { return m_groupCommitSize; } @@ -92,4 +92,4 @@ private: }; } /* namespace MOT */ -#endif /* GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H */ +#endif /* GROUP_SYNC_REDO_LOG_HANDLER_H */ diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.cpp similarity index 87% rename from src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.cpp rename to src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.cpp index ccf2d1708..2fe188100 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.cpp @@ -13,17 +13,17 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * segmented_group_synchronous_redo_log_handler.cpp + * segmented_group_sync_redo_log_handler.cpp * Implements a per-numa group commit redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.cpp + * group_synchronous_redo_log/segmented_group_sync_redo_log_handler.cpp * * ------------------------------------------------------------------------- */ -#include "segmented_group_synchronous_redo_log_handler.h" +#include "segmented_group_sync_redo_log_handler.h" #include "utilities.h" #include "mot_configuration.h" #include "session_context.h" @@ -32,25 +32,26 @@ namespace MOT { DECLARE_LOGGER(SegmentedGroupSyncRedoLogHandler, RedoLog); SegmentedGroupSyncRedoLogHandler::SegmentedGroupSyncRedoLogHandler() - : m_numaNodes(GetGlobalConfiguration().m_numaNodes), m_redoLogHandlerArray(NULL) + : m_numaNodes(GetGlobalConfiguration().m_numaNodes), m_redoLogHandlerArray(nullptr) {} bool SegmentedGroupSyncRedoLogHandler::Init() { m_redoLogHandlerArray = new (std::nothrow) GroupSyncRedoLogHandler[m_numaNodes]; - if (m_redoLogHandlerArray == NULL) { + if (m_redoLogHandlerArray == nullptr) { MOT_LOG_ERROR("Error allocating redoLogHandler array"); return false; } else { - for (unsigned int i = 0; i < m_numaNodes; i++) + for (unsigned int i = 0; i < m_numaNodes; i++) { m_redoLogHandlerArray[i].SetId(i); + } return true; } } SegmentedGroupSyncRedoLogHandler::~SegmentedGroupSyncRedoLogHandler() { - delete m_redoLogHandlerArray; + delete[] m_redoLogHandlerArray; } RedoLogBuffer* SegmentedGroupSyncRedoLogHandler::CreateBuffer() diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.h b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.h similarity index 86% rename from src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.h rename to src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.h index 9dedd1154..c4b5cb4dc 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/group_synchronous_redo_log/segmented_group_sync_redo_log_handler.h @@ -13,21 +13,21 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * segmented_group_synchronous_redo_log_handler.h + * segmented_group_sync_redo_log_handler.h * Implements a per-numa group commit redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * group_synchronous_redo_log/segmented_group_synchronous_redo_log_handler.h + * group_synchronous_redo_log/segmented_group_sync_redo_log_handler.h * * ------------------------------------------------------------------------- */ -#ifndef SEGMENTED_GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H -#define SEGMENTED_GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H +#ifndef SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_H +#define SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_H #include "redo_log_handler.h" -#include "group_synchronous_redo_log_handler.h" +#include "group_sync_redo_log_handler.h" namespace MOT { /** @@ -71,4 +71,4 @@ private: }; } // namespace MOT -#endif /* SEGMENTED_GROUP_SYNCHRONOUS_REDO_LOG_HANDLER_H */ +#endif /* SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_H */ diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.cpp index c44f6f6a7..de197ea29 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.cpp @@ -61,18 +61,18 @@ LogStatisticsProvider::LogStatisticsProvider() LogStatisticsProvider::~LogStatisticsProvider() { - ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void LogStatisticsProvider::RegisterProvider() { if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)ConfigManager::GetInstance().AddConfigChangeListener(this); } bool LogStatisticsProvider::CreateInstance() @@ -119,9 +119,9 @@ void LogStatisticsProvider::OnConfigChange() if (m_enable != GetGlobalConfiguration().m_enableLogStatistics) { m_enable = GetGlobalConfiguration().m_enableLogStatistics; if (m_enable) { - StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.h b/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.h index 13b23ff61..d97895deb 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/log_statistics.h @@ -30,7 +30,7 @@ #include "iconfig_change_listener.h" #include "numeric_statistic_variable.h" #include "statistics_provider.h" -#include "stats/frequency_statistic_variable.h" +#include "frequency_statistic_variable.h" #include "typed_statistics_generator.h" namespace MOT { @@ -79,13 +79,6 @@ private: }; class LogStatisticsProvider : public StatisticsProvider, public IConfigChangeListener { -private: - LogStatisticsProvider(); - virtual ~LogStatisticsProvider(); - - /** @brief Registers the provider in the manager. */ - void RegisterProvider(); - public: /** * @brief Creates singleton instance. Must be called once during engine startup. @@ -133,9 +126,15 @@ public: * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; private: + LogStatisticsProvider(); + ~LogStatisticsProvider() override; + + /** @brief Registers the provider in the manager. */ + void RegisterProvider(); + /** @var The single instance. */ static LogStatisticsProvider* m_provider; static TypedStatisticsGenerator m_generator; diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/logger_type.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/logger_type.cpp index 1c516aea7..2f15289e3 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/logger_type.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/logger_type.cpp @@ -22,7 +22,7 @@ * ------------------------------------------------------------------------- */ -#include +#include #include "logger_type.h" #include "utilities.h" diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.cpp new file mode 100644 index 000000000..1470ee179 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * pending_txn_logger.cpp + * Provides a redo logger interface for pending recovery transaction. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "mot_engine.h" +#include "pending_txn_logger.h" +#include "checkpoint_utils.h" + +namespace MOT { +DECLARE_LOGGER(RedoLog, PendingTxnLogger); + +PendingTxnLogger::PendingTxnLogger() : BaseTxnLogger(), m_fd(0) +{} + +PendingTxnLogger::~PendingTxnLogger() +{ + if (m_redoBuffer != nullptr) { + delete m_redoBuffer; + m_redoBuffer = nullptr; + } +} + +bool PendingTxnLogger::Init() +{ + RedoLogBuffer* buffer = new (std::nothrow) RedoLogBuffer(); + if (buffer == nullptr) { + return false; + } + + if (!buffer->Initialize()) { + delete buffer; + return false; + } + + m_redoBuffer = buffer; + return true; +} + +RC PendingTxnLogger::SerializePendingTransaction(TxnManager* txn, int fd) +{ + m_fd = fd; + RC status = RC_OK; + SetTxn(txn); + status = SerializeTransaction(); + if (status == RC_OK && !m_redoBuffer->Empty()) { + WritePartial(); + } + return status; +} + +void PendingTxnLogger::WriteToLog() +{ + if (!m_redoBuffer->Empty()) { + PendingTxnLogger::Header header; + uint32_t size = 0; + char* data = (char*)m_redoBuffer->Serialize(&size); + header.m_len = size; + header.m_replayLsn = m_txn->GetReplayLsn(); + (void)CheckpointUtils::WriteFile(m_fd, (const char*)&header, sizeof(PendingTxnLogger::Header)); + (void)CheckpointUtils::WriteFile(m_fd, (const char*)data, size); + ResetBuffer(); + } +} + +void PendingTxnLogger::ResetBuffer() +{ + if (likely(m_redoBuffer != nullptr)) { + m_redoBuffer->Reset(); + } +} + +} // namespace MOT \ No newline at end of file diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.h b/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.h new file mode 100644 index 000000000..9e09f5f05 --- /dev/null +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * pending_txn_logger.h + * Provides a redo logger interface for pending recovery transaction. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/core/system/transaction_logger/pending_txn_logger.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef PENDING_TXN_LOGGER_H +#define PENDING_TXN_LOGGER_H + +#include "redo_log_buffer.h" +#include "base_txn_logger.h" + +namespace MOT { +class MOTConfiguration; +class TxnManager; + +/** + * @class PendingTxnLogger + * @brief Provides a redo logger interface for pending recovery transaction. + */ +class PendingTxnLogger : public BaseTxnLogger { +public: + static const size_t MAGIC_NUMBER = 0xaabbccdd; + + PendingTxnLogger(); + PendingTxnLogger(const PendingTxnLogger& orig) = delete; + PendingTxnLogger& operator=(const PendingTxnLogger& orig) = delete; + ~PendingTxnLogger() override; + + /** + * @brief Initializes the the redo log buffer + * @return Boolean value that indicates success or failure + */ + bool Init(); + + RC SerializePendingTransaction(TxnManager* txn, int fd); + + /** + * @brief Writes buffer data to the logger + */ + void WriteToLog() override; + + /** + * @struct log entries header + */ + struct Header { + uint64_t m_magic = MAGIC_NUMBER; + uint64_t m_len; + uint64_t m_replayLsn; + }; + +private: + /** + * @brief Resets the redo log buffer + */ + void ResetBuffer(); + int m_fd; +}; +} // namespace MOT + +#endif /* PENDING_TXN_LOGGER_H */ diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.cpp index 5793b7058..f8651fe05 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.cpp @@ -23,8 +23,6 @@ */ #include "mot_engine.h" -#include "row.h" -#include "table.h" #include "redo_log_handler.h" #include "redo_log_writer.h" #include "redo_log.h" @@ -32,19 +30,19 @@ namespace MOT { DECLARE_LOGGER(RedoLog, RedoLog); -RedoLog::RedoLog(TxnManager* txn) - : m_redoLogHandler(nullptr), - m_redoBuffer(nullptr), - m_configuration(GetGlobalConfiguration()), - m_txn(txn), - m_flushed(false), - m_forceWrite(false) -{} +RedoLog::RedoLog(TxnManager* txn) : BaseTxnLogger(), m_redoLogHandler(nullptr), m_flushed(false), m_forceWrite(false) +{ + SetTxn(txn); +} RedoLog::~RedoLog() { - if (m_redoBuffer != nullptr) + if (m_redoBuffer != nullptr) { m_redoLogHandler->DestroyBuffer(m_redoBuffer); + m_redoBuffer = nullptr; + } + + m_redoLogHandler = nullptr; } bool RedoLog::Init() @@ -54,8 +52,9 @@ bool RedoLog::Init() if (m_redoLogHandler != nullptr) { m_redoBuffer = m_redoLogHandler->CreateBuffer(); } - if (m_redoBuffer == nullptr) + if (m_redoBuffer == nullptr) { return false; + } ResetBuffer(); } return true; @@ -127,149 +126,6 @@ void RedoLog::CommitPrepared() } } -RC RedoLog::InsertRow(Row* row) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - MaxKey key; - MOT::Index* index = row->GetTable()->GetPrimaryIndex(); - key.InitKey(index->GetKeyLength()); - index->BuildKey(row->GetTable(), row, &key); - uint64_t tableId = row->GetTable()->GetTableId(); - uint64_t exId = row->GetTable()->GetTableExId(); - bool success = RedoLogWriter::AppendCreateRow( - *m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId, row->GetRowId()); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendCreateRow( - *m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId, row->GetRowId()); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::OverwriteRow(Row* row) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - MaxKey key; - MOT::Index* index = row->GetTable()->GetPrimaryIndex(); - key.InitKey(index->GetKeyLength()); - index->BuildKey(row->GetTable(), row, &key); - uint64_t tableId = row->GetTable()->GetTableId(); - uint64_t exId = row->GetTable()->GetTableExId(); - bool success = - RedoLogWriter::AppendOverwriteRow(*m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId); - if (!success) { - WritePartial(); - success = - RedoLogWriter::AppendOverwriteRow(*m_redoBuffer, tableId, &key, row->GetData(), row->GetTupleSize(), exId); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::UpdateRow(Row* row, BitmapSet& modifiedColumns) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - - bool success = RedoLogWriter::AppendUpdate(*m_redoBuffer, row, &modifiedColumns); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendUpdate(*m_redoBuffer, row, &modifiedColumns); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::DeleteRow(Row* row) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - MaxKey key; - MOT::Index* index = row->GetTable()->GetPrimaryIndex(); - key.InitKey(index->GetKeyLength()); - index->BuildKey(row->GetTable(), row, &key); - uint64_t tableId = row->GetTable()->GetTableId(); - uint64_t exId = row->GetTable()->GetTableExId(); - uint64_t version = row->GetCommitSequenceNumber(); - bool success = RedoLogWriter::AppendRemove(*m_redoBuffer, tableId, &key, exId, version); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendRemove(*m_redoBuffer, tableId, &key, exId, version); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::CreateTable(Table* table) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - bool success = RedoLogWriter::AppendTable(*m_redoBuffer, table); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendTable(*m_redoBuffer, table); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::DropTable(Table* table) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - bool success = RedoLogWriter::AppendDropTable(*m_redoBuffer, table); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendDropTable(*m_redoBuffer, table); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::CreateIndex(MOT::Index* index) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - bool success = RedoLogWriter::AppendIndex(*m_redoBuffer, index->GetTable(), index); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendIndex(*m_redoBuffer, index->GetTable(), index); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::DropIndex(MOT::Index* index) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - // No need to serialize delete of primary index. Delete of primary index can - // only happen in case of drop table. A drop table operation will follow the - // drop primary index operation so can disregard the drop primary index. - if (index->IsPrimaryKey()) - return RC_OK; - bool success = RedoLogWriter::AppendDropIndex(*m_redoBuffer, index->GetTable(), index); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendDropIndex(*m_redoBuffer, index->GetTable(), index); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -RC RedoLog::TruncateTable(Table* table) -{ - if (!m_configuration.m_enableRedoLog) - return RC_OK; - bool success = RedoLogWriter::AppendTruncateTable(*m_redoBuffer, table); - if (!success) { - WritePartial(); - success = RedoLogWriter::AppendTruncateTable(*m_redoBuffer, table); - } - return (success == true) ? RC_OK : RC_ERROR; -} - -void RedoLog::WritePartial() -{ - RedoLogWriter::AppendPartial(*m_redoBuffer, m_txn); - WriteToLog(); -} - void RedoLog::WriteToLog() { if (!m_redoBuffer->Empty() || m_forceWrite) { @@ -281,179 +137,6 @@ void RedoLog::WriteToLog() } } -RC RedoLog::SerializeDropIndex(TxnDDLAccess::DDLAccess* ddlAccess, bool hasDML, IdxDDLAccessMap& idxDDLMap) -{ - RC status = RC_ERROR; - MOT::Index* index = (MOT::Index*)ddlAccess->GetEntry(); - if (!hasDML || !(!index->IsPrimaryKey() && index->IsUnique())) { - status = DropIndex(index); - } else { - IdxDDLAccessMap::iterator it = idxDDLMap.find(index->GetExtId()); - if (it != idxDDLMap.end()) { - // we create and drop no need for both them - MOT_LOG_DEBUG("Erase create index: %s %lu", index->GetName().c_str(), index->GetExtId()); - idxDDLMap.erase(it); - status = RC_OK; - } else { - idxDDLMap[index->GetExtId()] = ddlAccess; - status = DropIndex(index); - } - } - return status; -} - -RC RedoLog::SerializeDDLs(IdxDDLAccessMap& idxDDLMap) -{ - MOT::Index* index = nullptr; - bool hasDML = (m_txn->m_accessMgr->m_rowCnt > 0 && !m_txn->m_isLightSession); - TxnDDLAccess* transactionDDLAccess = m_txn->m_txnDdlAccess; - if (transactionDDLAccess != nullptr && transactionDDLAccess->Size() > 0) { - RC status = RC_ERROR; - for (uint16_t i = 0; i < transactionDDLAccess->Size(); i++) { - Table* truncatedTable = nullptr; - TxnDDLAccess::DDLAccess* ddlAccess = transactionDDLAccess->Get(i); - if (ddlAccess == nullptr) { - return RC_ERROR; - } - DDLAccessType accessType = ddlAccess->GetDDLAccessType(); - switch (accessType) { - case DDL_ACCESS_CREATE_TABLE: - status = CreateTable((Table*)ddlAccess->GetEntry()); - break; - - case DDL_ACCESS_DROP_TABLE: - status = DropTable((Table*)ddlAccess->GetEntry()); - break; - - case DDL_ACCESS_CREATE_INDEX: - index = (MOT::Index*)ddlAccess->GetEntry(); - if (!hasDML || !(!index->IsPrimaryKey() && index->IsUnique())) { - status = CreateIndex(index); - } else { - // in case of unique secondary skip and send it after DML - MOT_LOG_DEBUG("Defer create index: %s %lu", index->GetName().c_str(), index->GetExtId()); - idxDDLMap[index->GetExtId()] = ddlAccess; - status = RC_OK; - } - break; - - case DDL_ACCESS_DROP_INDEX: - status = SerializeDropIndex(ddlAccess, hasDML, idxDDLMap); - break; - case DDL_ACCESS_TRUNCATE_TABLE: - // in case of truncate table the DDLAccess entry holds the - // the old indexes. We need to serialize the tableId. In this - // case we take it from the ddl access Oid. - truncatedTable = GetTableManager()->GetTableByExternal(ddlAccess->GetOid()); - if (truncatedTable == nullptr) { - // This should not happen. Truncate table is protected - // by lock. While doing truncate table, the table cannot - // not be removed - return RC_ERROR; - } - status = TruncateTable(truncatedTable); - break; - default: - return RC_ERROR; - break; - } - if (status != RC_OK) { - MOT_LOG_ERROR("Serialize DDL finished with error: %d", status); - return RC_ERROR; - } - } - } - - return RC_OK; -} - -RC RedoLog::SerializeDMLs() -{ - RC status = RC_OK; - TxnOrderedSet_t& orderedSet = m_txn->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - Access* access = raPair.second; - if (access != nullptr) { - switch (access->m_type) { - case INS: - if (access->m_params.IsPrimarySentinel()) { - if (access->m_params.IsUpgradeInsert()) { - if (access->m_params.IsDummyDeletedRow() == false) { - status = DeleteRow(access->m_localRow); - if (status != RC_OK) { - return status; - } - } - } - status = InsertRow(access->GetTxnRow()); - } - break; - case DEL: - if (access->m_params.IsPrimarySentinel()) { - status = DeleteRow(access->GetTxnRow()); - } - break; - case WR: - status = UpdateRow(access->GetTxnRow(), access->m_modifiedColumns); - break; - default: - break; - } - } - if (status != RC_OK) { - MOT_LOG_ERROR("Serialize DML finished with error: %d", status); - return status; - } - } - - return RC_OK; -} - -RC RedoLog::SerializeTransaction() -{ - IdxDDLAccessMap idxDDLMap; - RC status = SerializeDDLs(idxDDLMap); - if (status != RC_OK) { - MOT_LOG_ERROR("Failed to serialize DDLs: %d", status); - return status; - } - - if (m_txn->m_isLightSession) { - MOT_LOG_DEBUG("Serialize DDL light session finished"); - return RC_OK; - } - - status = SerializeDMLs(); - if (status != RC_OK) { - MOT_LOG_ERROR("Failed to serialize DMLs: %d", status); - return status; - } - - // create operations for unique indexes - IdxDDLAccessMap::iterator it = idxDDLMap.begin(); - while (it != idxDDLMap.end()) { - DDLAccessType accessType = it->second->GetDDLAccessType(); - switch (accessType) { - case DDL_ACCESS_CREATE_INDEX: { - MOT::Index* index = (MOT::Index*)it->second->GetEntry(); - MOT_LOG_DEBUG("Send create index: %s %lu", index->GetName().c_str(), index->GetExtId()); - status = CreateIndex(index); - break; - } - default: - break; - } - - if (status != RC_OK) { - return RC_ERROR; - } - - it++; - } - idxDDLMap.clear(); - return RC_OK; -} - void RedoLog::ResetBuffer() { if (m_configuration.m_enableRedoLog && m_redoLogHandler) { @@ -465,4 +148,5 @@ void RedoLog::ResetBuffer() } } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.h index abac71d1a..bb6463911 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log.h @@ -28,12 +28,15 @@ #include "redo_log_buffer.h" #include "bitmapset.h" #include "txn_ddl_access.h" +#include "base_txn_logger.h" namespace MOT { class MOTConfiguration; class RedoLogHandler; class TxnManager; class Table; +class DDLAlterTableRenameColumn; +class Column; class Row; class Index; @@ -41,11 +44,13 @@ class Index; * @class RedoLog * @brief Provides a redo logger interface */ -class RedoLog { +class RedoLog : public BaseTxnLogger { public: - RedoLog(TxnManager* txn); + explicit RedoLog(TxnManager* txn); RedoLog(const RedoLog& orig) = delete; - virtual ~RedoLog(); + RedoLog& operator=(const RedoLog& orig) = delete; + ~RedoLog() override; + /** * @brief Initializes the redo log state and creates * the redo log buffer @@ -90,120 +95,19 @@ public: */ void RollbackPrepared(); - /** - * @brief Appends a row into the redo log buffer - * @param row the row to insert - * @return The status of the operation. - */ - RC InsertRow(Row* row); - - /** - * @brief Inserts an updated row into the redo log buffer - * @param row The updaed row - * @return The status of the operation. - */ - RC OverwriteRow(Row* row); - - /** - * @brief Delta updates a row - * @param row The updated row - * @param modifiedColumns The delta columns - * @return The status of the operation. - */ - RC UpdateRow(Row* row, BitmapSet& modifiedColumns); - - /** - * @brief Inserts a delete row to the redo log buffer - * @param row The row to delete - * @return The status of the operation. - */ - RC DeleteRow(Row* row); - - /** - * @brief Inserts a table's creation data to the redo log buffer - * @param table The table to add. - * @return The status of the operation. - */ - RC CreateTable(Table* table); - - /** - * @brief Inserts a delete table op into the redo log buffer - * @param table the table to delete. - * @return RC value that indicates the status of the operation. - */ - RC DropTable(Table* table); - - /** - * @brief Inserts a create index op into the redo log buffer - * @param index The index to create. - * @return The status of the operation. - */ - RC CreateIndex(Index* index); - - /** - * @brief inserts a drop index op into the redo log buffer - * @param index the index to delete. - * @return RC value that indicates the status of the operation. - */ - RC DropIndex(Index* index); - - /** - * @brief inserts a trucate table op into the redo log buffer - * @param tablethe table to turncate. - * @return RC value that indicates the status of the operation. - */ - RC TruncateTable(Table* table); - - /** - * @brief Writes the whole transaction's DDLs and DMLs into the redo buffer - * @return The status of the operation. - */ - RC SerializeTransaction(); - /** * @brief Writes buffer data to the logger */ - void WriteToLog(); - - inline RedoLogBuffer& GetBuffer() - { - return *m_redoBuffer; - } + void WriteToLog() override; private: - /* Map of Index ID to DDLAccess. */ - typedef std::map IdxDDLAccessMap; - - /** - * @brief When the buffer is full, flush its contents to the log - */ - void WritePartial(); - /** * @brief Resets the redo log buffer */ void ResetBuffer(); - /** - * @brief Writes the whole transaction's DDLs into the redo buffer - * @param[out] idxDDLMap Map of Index ID to DDLAccess. - * @return The status of the operation. - */ - RC SerializeDDLs(IdxDDLAccessMap& idxDDLMap); - - RC SerializeDropIndex(TxnDDLAccess::DDLAccess* ddlAccess, bool hasDML, IdxDDLAccessMap& idxDDLMap); - - /** - * @brief Writes the whole transaction's DMLs into the redo buffer - * @return The status of the operation. - */ - RC SerializeDMLs(); - /* Member variables */ RedoLogHandler* m_redoLogHandler; - RedoLogBuffer* m_redoBuffer; - MOTConfiguration& m_configuration; - TxnManager* m_txn; bool m_flushed; bool m_forceWrite; }; diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_buffer.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_buffer.h index c04cba4bd..8f8e5fc41 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_buffer.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_buffer.h @@ -25,8 +25,8 @@ #ifndef REDO_LOG_BUFFER_H #define REDO_LOG_BUFFER_H -#include -#include +#include +#include #include #include #include @@ -41,11 +41,7 @@ namespace MOT { class RedoLogBuffer { public: inline RedoLogBuffer() - : m_bufferSize(REDO_DEFAULT_BUFFER_SIZE), - m_nextFree(sizeof(uint32_t)), - m_buffer(nullptr), - m_allocated(false), - m_next(nullptr) + : m_bufferSize(REDO_DEFAULT_BUFFER_SIZE), m_nextFree(sizeof(uint32_t)), m_buffer(nullptr), m_allocated(false) {} inline bool Initialize() @@ -62,9 +58,13 @@ public: RedoLogBuffer(const RedoLogBuffer& orig) = delete; RedoLogBuffer(RedoLogBuffer&& other) - : m_bufferSize(other.m_bufferSize), m_nextFree(other.m_nextFree), m_buffer(other.m_buffer) + : m_bufferSize(other.m_bufferSize), + m_nextFree(other.m_nextFree), + m_buffer(other.m_buffer), + m_allocated(other.m_allocated) { other.m_buffer = nullptr; + other.m_allocated = false; } inline ~RedoLogBuffer() @@ -72,6 +72,8 @@ public: if (m_buffer != nullptr) { delete[] m_buffer; } + m_buffer = nullptr; + m_allocated = false; } RedoLogBuffer& operator=(RedoLogBuffer&& other) @@ -85,7 +87,9 @@ public: m_bufferSize = other.m_bufferSize; m_nextFree = other.m_nextFree; m_buffer = other.m_buffer; + m_allocated = other.m_allocated; other.m_buffer = nullptr; + other.m_allocated = false; return *this; } @@ -133,19 +137,6 @@ public: m_nextFree += size; } - /** - * @brief Appends raw data from input stream. - * @param is The input stream - * @param size The amount of bytes to append from the stream. - */ - void Append(std::istream& is, uint32_t size) - { - MOT_ASSERT(m_nextFree + size <= m_bufferSize); - char* ptr = (char*)&m_buffer[m_nextFree]; - is.read(ptr, size); - m_nextFree += size; - } - /** * @brief Appends typed items to the buffer. * @param x1 The first typed item to append. @@ -287,23 +278,14 @@ public: std::stringstream ss; ss << std::hex; for (uint32_t i = 0; i < len; i++) { - if (m_buffer[i] < 16) + if (m_buffer[i] < 16) { ss << "0"; + } ss << (uint32_t)m_buffer[i] << "::"; } return ss.str(); } - inline void SetNext(RedoLogBuffer* nextBuffer) - { - m_next = nextBuffer; - } - - inline RedoLogBuffer* GetNext() - { - return m_next; - } - /** @define By default use 1 MB buffers for redo log. */ static constexpr uint32_t REDO_DEFAULT_BUFFER_SIZE = 1024 * 1024; @@ -319,9 +301,6 @@ private: /** @var Indicates whether buffer was allocated. */ bool m_allocated; - - /** @var Manages in-place list of objects. */ - RedoLogBuffer* m_next; }; } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_global.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_global.h index d83ba218d..17d3c3716 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_global.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_global.h @@ -42,7 +42,7 @@ enum OperationCode : uint16_t { UPDATE_ROW_VARIABLE = 3, /** @var Full overwrite an existing row. */ - OVERWRITE_ROW = 4, + OVERWRITE_ROW = 4, // Deprecated /** @var Remove a row by primary key. */ REMOVE_ROW = 5, @@ -50,8 +50,8 @@ enum OperationCode : uint16_t { /** @var Prepare a transaction. */ PREPARE_TX = 6, - /** @var Commit a transaction. */ - COMMIT_TX = 7, + /** @var Commit a transaction (1VCC), this is deprecated. */ + COMMIT_TX_1VCC = 7, /** @var Commit Prepared transaction. */ COMMIT_PREPARED_TX = 8, @@ -62,24 +62,51 @@ enum OperationCode : uint16_t { /** @var Rollback Prepared transaction. */ ROLLBACK_PREPARED_TX = 10, - /** @var partial redo. */ - PARTIAL_REDO_TX = 11, + /** @var Partial redo (1VCC), this is deprecated. */ + PARTIAL_REDO_TX_1VCC = 11, - /** @var table creation. */ + /** @var Create table. */ CREATE_TABLE = 12, - /** @var table drop. */ + /** @var Drop table. */ DROP_TABLE = 13, - /** @var index creation. */ + /** @var Create index. */ CREATE_INDEX = 14, - /** @var index drop. */ + /** @var Drop index. */ DROP_INDEX = 15, - /** @var truncate table. */ + /** @var Truncate table. */ TRUNCATE_TABLE = 16, + /** @var Alter table add column*/ + ALTER_TABLE_ADD_COLUMN = 17, + + /** @var Alter table drop column*/ + ALTER_TABLE_DROP_COLUMN = 18, + + /** @var Alter table drop column*/ + ALTER_TABLE_RENAME_COLUMN = 19, + + /** @var Commit a transaction (MVCC). */ + COMMIT_TX = 20, + + /** @var Partial redo (MVCC). */ + PARTIAL_REDO_TX = 21, + + /** @var Commit a transaction with DDL(s) in it. */ + COMMIT_DDL_TX = 22, + + /** @var Partial redo for a transaction with DDL(s) in it. */ + PARTIAL_REDO_DDL_TX = 23, + + /** @var Commit a cross engine transaction. */ + COMMIT_CROSS_TX = 24, + + /** @var Partial redo for a cross engine transaction. */ + PARTIAL_REDO_CROSS_TX = 25, + /** @var This must be the last entry. */ INVALID_OPERATION_CODE }; @@ -99,16 +126,16 @@ inline const char* OperationCodeToString(OperationCode op) return "REMOVE_ROW"; case OperationCode::PREPARE_TX: return "PREPARE_TX"; - case OperationCode::COMMIT_TX: - return "COMMIT_TX"; + case OperationCode::COMMIT_TX_1VCC: + return "COMMIT_TX_1VCC"; case OperationCode::COMMIT_PREPARED_TX: return "COMMIT_PREPARED_TX"; case OperationCode::ROLLBACK_TX: return "ROLLBACK_TX"; case OperationCode::ROLLBACK_PREPARED_TX: return "ROLLBACK_PREPARED_TX"; - case OperationCode::PARTIAL_REDO_TX: - return "PARTIAL_REDO_TX"; + case OperationCode::PARTIAL_REDO_TX_1VCC: + return "PARTIAL_REDO_TX_1VCC"; case OperationCode::CREATE_TABLE: return "CREATE_TABLE"; case OperationCode::CREATE_INDEX: @@ -117,6 +144,26 @@ inline const char* OperationCodeToString(OperationCode op) return "DROP_TABLE"; case OperationCode::DROP_INDEX: return "DROP_INDEX"; + case OperationCode::TRUNCATE_TABLE: + return "TRUNCATE_TABLE"; + case OperationCode::ALTER_TABLE_ADD_COLUMN: + return "ALTER_TABLE_ADD_COLUMN"; + case OperationCode::ALTER_TABLE_DROP_COLUMN: + return "ALTER_TABLE_DROP_COLUMN"; + case OperationCode::ALTER_TABLE_RENAME_COLUMN: + return "ALTER_TABLE_RENAME_COLUMN"; + case OperationCode::COMMIT_TX: + return "COMMIT_TX"; + case OperationCode::PARTIAL_REDO_TX: + return "PARTIAL_REDO_TX"; + case OperationCode::COMMIT_DDL_TX: + return "COMMIT_DDL_TX"; + case OperationCode::PARTIAL_REDO_DDL_TX: + return "PARTIAL_REDO_DDL_TX"; + case OperationCode::COMMIT_CROSS_TX: + return "COMMIT_CROSS_TX"; + case OperationCode::PARTIAL_REDO_CROSS_TX: + return "PARTIAL_REDO_CROSS_TX"; case OperationCode::INVALID_OPERATION_CODE: default: return "UNKNOWN_OP_CODE"; @@ -130,7 +177,14 @@ inline bool IsAbortOp(OperationCode op) inline bool IsCommitOp(OperationCode op) { - return (op == OperationCode::COMMIT_TX || op == OperationCode::COMMIT_PREPARED_TX); + return (op == OperationCode::COMMIT_TX || op == OperationCode::COMMIT_DDL_TX || + op == OperationCode::COMMIT_CROSS_TX || op == OperationCode::COMMIT_TX_1VCC || + op == OperationCode::COMMIT_PREPARED_TX); +} + +inline bool Is1VCCEndSegmentOpCode(OperationCode op) +{ + return (op == OperationCode::COMMIT_TX_1VCC || op == OperationCode::PARTIAL_REDO_TX_1VCC); } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.cpp index 5a385c8e3..14294c6f2 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.cpp @@ -29,8 +29,8 @@ #include "redo_log_handler.h" #include "logger_factory.h" #include "logger_type.h" -#include "synchronous_redo_log_handler.h" -#include "segmented_group_synchronous_redo_log_handler.h" +#include "sync_redo_log_handler.h" +#include "segmented_group_sync_redo_log_handler.h" #include "mot_error.h" namespace MOT { @@ -48,11 +48,11 @@ RedoLogHandler* RedoLogHandlerFactory::CreateRedoLogHandler() RedoLogHandler* handler = nullptr; switch (cfg.m_redoLogHandlerType) { - case RedoLogHandlerType::NONE: + case RedoLogHandlerType::NONE_REDO_LOG_HANDLER: handler = nullptr; break; case RedoLogHandlerType::SYNC_REDO_LOG_HANDLER: - handler = new (std::nothrow) SynchronousRedoLogHandler(); + handler = new (std::nothrow) SyncRedoLogHandler(); break; case RedoLogHandlerType::SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER: handler = new (std::nothrow) SegmentedGroupSyncRedoLogHandler(); @@ -66,7 +66,7 @@ RedoLogHandler* RedoLogHandlerFactory::CreateRedoLogHandler() return handler; } - if ((cfg.m_redoLogHandlerType != RedoLogHandlerType::NONE) && (handler == nullptr)) { + if ((cfg.m_redoLogHandlerType != RedoLogHandlerType::NONE_REDO_LOG_HANDLER) && (handler == nullptr)) { MOT_REPORT_PANIC(MOT_ERROR_OOM, "Redo Log Handler Initialization", "Failed to allocate memory for redo log handler, aborting"); @@ -83,8 +83,11 @@ RedoLogHandler::~RedoLogHandler() if (m_logger != nullptr) { m_logger->FlushLog(); m_logger->CloseLog(); - if (GetGlobalConfiguration().m_loggerType != LoggerType::EXTERNAL_LOGGER) + if (GetGlobalConfiguration().m_loggerType != LoggerType::EXTERNAL_LOGGER) { delete m_logger; + } + m_logger = nullptr; } + m_wakeupFunc = nullptr; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.h index fbeb47b81..37f58ea5f 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler.h @@ -42,6 +42,7 @@ class RedoLogHandler { public: RedoLogHandler(); RedoLogHandler(const RedoLogHandler& orig) = delete; + RedoLogHandler& operator=(const RedoLogHandler& orig) = delete; virtual ~RedoLogHandler(); virtual bool Init() @@ -118,7 +119,7 @@ protected: ILogger* m_logger; RwLock m_redo_lock; - inline void WakeupWalWriter() + inline void WakeupWalWriter() const { if (m_wakeupFunc != nullptr) { m_wakeupFunc(); diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.cpp index 77ff52f49..04a6f7de4 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.cpp @@ -25,15 +25,15 @@ #include "redo_log_handler_type.h" #include "utilities.h" -#include +#include namespace MOT { DECLARE_LOGGER(RedoLogHandlerType, RedoLog) -static const char* NONE_STR = "none"; -static const char* SYNC_REDO_LOG_HANDLER_STR = "synchronous"; -static const char* SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_STR = "segmented_group_synchronous"; -static const char* INVALID_REDO_LOG_HANDLER_STR = "INVALID"; +static const char* const NONE_STR = "none"; +static const char* const SYNC_REDO_LOG_HANDLER_STR = "synchronous"; +static const char* const SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_STR = "segmented_group_synchronous"; +static const char* const INVALID_REDO_LOG_HANDLER_STR = "INVALID"; static const char* redoLogHandlerTypeNames[] = { NONE_STR, SYNC_REDO_LOG_HANDLER_STR, SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_STR}; @@ -43,7 +43,7 @@ RedoLogHandlerType RedoLogHandlerTypeFromString(const char* redoLogHandlerType) RedoLogHandlerType handlerType = RedoLogHandlerType::INVALID_REDO_LOG_HANDLER; if (strcmp(redoLogHandlerType, NONE_STR) == 0) { - handlerType = RedoLogHandlerType::NONE; + handlerType = RedoLogHandlerType::NONE_REDO_LOG_HANDLER; } else if (strcmp(redoLogHandlerType, SYNC_REDO_LOG_HANDLER_STR) == 0) { handlerType = RedoLogHandlerType::SYNC_REDO_LOG_HANDLER; } else if (strcmp(redoLogHandlerType, SEGMENTED_GROUP_SYNC_REDO_LOG_HANDLER_STR) == 0) { diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.h index 96c7d4e74..3ebd1c37f 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_handler_type.h @@ -31,7 +31,7 @@ namespace MOT { enum class RedoLogHandlerType : uint32_t { /** @var Denotes no redo log. */ - NONE = 0, + NONE_REDO_LOG_HANDLER = 0, /** @var Denotes SynchronousRedoLogHandler. */ SYNC_REDO_LOG_HANDLER, diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.cpp index 6f18089e2..0aa2751c1 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.cpp @@ -36,43 +36,23 @@ namespace MOT { DECLARE_LOGGER(RedoLog, TxnLogger); -bool RedoLogWriter::AppendUpdate(RedoLogBuffer& redoLogBuffer, uint64_t table, Key* primaryKey, uint64_t attr, - uint64_t attrSize, const void* newValue, uint64_t externalId) -{ - uint64_t entrySize = sizeof(OperationCode) + sizeof(uint32_t) + sizeof(table) + sizeof(externalId) + - primaryKey->GetKeyLength() + sizeof(attr) + sizeof(attrSize) + attrSize; - entrySize += sizeof(EndSegmentBlock); - if (redoLogBuffer.FreeSize() < entrySize) { - return false; - } - - uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; - redoLogBuffer.Append(OperationCode::UPDATE_ROW_VARIABLE); - redoLogBuffer.Append(metaVersion); - redoLogBuffer.Append(table); - redoLogBuffer.Append(externalId); - redoLogBuffer.Append(primaryKey->GetKeyBuf(), primaryKey->GetKeyLength()); - redoLogBuffer.Append(attr); - redoLogBuffer.Append(attrSize); - redoLogBuffer.Append(newValue, attrSize); - return true; -} - -bool RedoLogWriter::AppendUpdate(RedoLogBuffer& redoLogBuffer, Row* row, BitmapSet* modifiedColumns) +bool RedoLogWriter::AppendUpdate( + RedoLogBuffer& redoLogBuffer, Row* row, BitmapSet* modifiedColumns, uint64_t previous_version, bool idxUpd) { MaxKey maxKey; - Index* index = row->GetTable()->GetPrimaryIndex(); + MOT::Index* index = row->GetTable()->GetPrimaryIndex(); maxKey.InitKey(index->GetKeyLength()); index->BuildKey(row->GetTable(), row, &maxKey); Table* table = row->GetTable(); uint64_t tableId = table->GetTableId(); uint64_t externalId = table->GetTableExId(); - uint64_t version = row->GetCommitSequenceNumber(); uint64_t entrySize = sizeof(OperationCode) + sizeof(uint32_t) + sizeof(tableId) + sizeof(externalId) + - sizeof(uint16_t) /* key_length */ + index->GetKeyLength() + sizeof(version) + - modifiedColumns->GetLength() /* updated columns bitmap */ - + modifiedColumns->GetLength() /* null fields bitmap (should be the same length) */ - + row->GetTupleSize(); /* not accurate, avoid double looping on updated column */ + sizeof(uint16_t) /* key_length */ + index->GetKeyLength() + + sizeof(uint64_t) /* prev row version */ + + sizeof(bool) + /* indicates if indexed column was updated */ + modifiedColumns->GetLength() + /* updated columns bitmap */ + modifiedColumns->GetLength() + /* null fields bitmap (should be the same length) */ + row->GetTupleSize(); /* not accurate, avoid double looping on updated column */ entrySize += sizeof(EndSegmentBlock); if (redoLogBuffer.FreeSize() < entrySize) { return false; @@ -90,44 +70,22 @@ bool RedoLogWriter::AppendUpdate(RedoLogBuffer& redoLogBuffer, Row* row, BitmapS redoLogBuffer.Append(externalId); redoLogBuffer.Append(key->GetKeyLength()); redoLogBuffer.Append(key->GetKeyBuf(), key->GetKeyLength()); - redoLogBuffer.Append(version); + redoLogBuffer.Append(previous_version); + redoLogBuffer.Append(idxUpd); redoLogBuffer.Append(modifiedColumns->GetData(), modifiedColumns->GetLength()); redoLogBuffer.Append(validBitmap.GetData(), validBitmap.GetLength()); - it.Start(); + (void)it.Start(); while (!it.End()) { if (it.IsSet() && validBitmap.GetBit(it.GetPosition())) { Column* column = table->GetField(it.GetPosition() + 1); redoLogBuffer.Append((uint8_t*)(rowData + column->m_offset), column->m_size); } - it.Next(); + (void)it.Next(); } return true; } -bool RedoLogWriter::AppendOverwriteRow(RedoLogBuffer& redoLogBuffer, uint64_t table, Key* primaryKey, - const void* rowData, uint64_t rowDataSize, uint64_t externalId) -{ - uint64_t entrySize = sizeof(OperationCode) + sizeof(uint32_t) + sizeof(table) + sizeof(externalId) + - sizeof(uint16_t) /* key_length */ + primaryKey->GetKeyLength() + sizeof(rowDataSize) + - rowDataSize; - entrySize += sizeof(EndSegmentBlock); - if (redoLogBuffer.FreeSize() < entrySize) { - return false; - } - - uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; - redoLogBuffer.Append(OperationCode::OVERWRITE_ROW); - redoLogBuffer.Append(metaVersion); - redoLogBuffer.Append(table); - redoLogBuffer.Append(externalId); - redoLogBuffer.Append(primaryKey->GetKeyLength()); - redoLogBuffer.Append(primaryKey->GetKeyBuf(), primaryKey->GetKeyLength()); - redoLogBuffer.Append(rowDataSize); - redoLogBuffer.Append(rowData, rowDataSize); - return true; -} - bool RedoLogWriter::AppendCreateRow(RedoLogBuffer& redoLogBuffer, uint64_t table, Key* primaryKey, const void* rowData, uint64_t rowDataSize, uint64_t externalId, uint64_t rowId) { @@ -153,7 +111,7 @@ bool RedoLogWriter::AppendCreateRow(RedoLogBuffer& redoLogBuffer, uint64_t table } bool RedoLogWriter::AppendRemove( - RedoLogBuffer& redoLogBuffer, uint64_t table, const Key* primaryKey, uint64_t externalId, uint64_t version) + RedoLogBuffer& redoLogBuffer, uint64_t table, const Key* primaryKey, uint64_t version, uint64_t externalId) { uint64_t entrySize = sizeof(OperationCode) + sizeof(uint32_t) + sizeof(table) + sizeof(externalId) + sizeof(uint16_t) /* key_length */ + primaryKey->GetKeyLength() + sizeof(version); @@ -173,59 +131,97 @@ bool RedoLogWriter::AppendRemove( return true; } -bool RedoLogWriter::AppendCommit(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +void RedoLogWriter::AppendCommit(RedoLogBuffer& redoLogBuffer, TxnManager* txn) { - // buffer must have enough space for control entry - EndSegmentBlock block(OperationCode::COMMIT_TX, - txn->GetCommitSequenceNumber(), - txn->GetTransactionId(), - txn->GetInternalTransactionId()); - redoLogBuffer.Append(block); - return true; -} + uint16_t flags = 0; + OperationCode opCode; + if (txn->hasDDL()) { + opCode = OperationCode::COMMIT_DDL_TX; + } else if (txn->IsCrossEngineTxn()) { + opCode = OperationCode::COMMIT_CROSS_TX; + } else { + opCode = OperationCode::COMMIT_TX; + } -bool RedoLogWriter::AppendPrepare(RedoLogBuffer& redoLogBuffer, TxnManager* txn) -{ - // buffer must have enough space for control entry - EndSegmentBlock block(OperationCode::PREPARE_TX, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); - redoLogBuffer.Append(block); - return true; -} - -bool RedoLogWriter::AppendCommitPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn) -{ - // buffer must have enough space for control entry - EndSegmentBlock block(OperationCode::COMMIT_PREPARED_TX, - txn->GetCommitSequenceNumber(), - txn->GetTransactionId(), - txn->GetInternalTransactionId()); - redoLogBuffer.Append(block); - return true; -} - -bool RedoLogWriter::AppendRollbackPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn) -{ + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } // buffer must have enough space for control entry EndSegmentBlock block( - OperationCode::ROLLBACK_PREPARED_TX, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); + opCode, flags, txn->GetCommitSequenceNumber(), txn->GetTransactionId(), txn->GetInternalTransactionId()); redoLogBuffer.Append(block); - return true; } -bool RedoLogWriter::AppendRollback(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +void RedoLogWriter::AppendPrepare(RedoLogBuffer& redoLogBuffer, TxnManager* txn) { + uint16_t flags = 0; + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } // buffer must have enough space for control entry - EndSegmentBlock block(OperationCode::ROLLBACK_TX, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); + EndSegmentBlock block( + OperationCode::PREPARE_TX, flags, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); redoLogBuffer.Append(block); - return true; } -bool RedoLogWriter::AppendPartial(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +void RedoLogWriter::AppendCommitPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn) { + uint16_t flags = 0; + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } // buffer must have enough space for control entry - EndSegmentBlock block(OperationCode::PARTIAL_REDO_TX, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); + EndSegmentBlock block(OperationCode::COMMIT_PREPARED_TX, + flags, + txn->GetCommitSequenceNumber(), + txn->GetTransactionId(), + txn->GetInternalTransactionId()); + redoLogBuffer.Append(block); +} + +void RedoLogWriter::AppendRollbackPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +{ + uint16_t flags = 0; + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } + // buffer must have enough space for control entry + EndSegmentBlock block( + OperationCode::ROLLBACK_PREPARED_TX, flags, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); + redoLogBuffer.Append(block); +} + +void RedoLogWriter::AppendRollback(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +{ + uint16_t flags = 0; + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } + // buffer must have enough space for control entry + EndSegmentBlock block( + OperationCode::ROLLBACK_TX, flags, 0, txn->GetTransactionId(), txn->GetInternalTransactionId()); + redoLogBuffer.Append(block); +} + +void RedoLogWriter::AppendPartial(RedoLogBuffer& redoLogBuffer, TxnManager* txn) +{ + uint16_t flags = 0; + OperationCode opCode; + if (txn->hasDDL()) { + opCode = OperationCode::PARTIAL_REDO_DDL_TX; + } else if (txn->IsCrossEngineTxn()) { + opCode = OperationCode::PARTIAL_REDO_CROSS_TX; + } else { + opCode = OperationCode::PARTIAL_REDO_TX; + } + + if (txn->IsUpdateIndexColumn()) { + flags |= EndSegmentBlock::MOT_UPDATE_INDEX_COLUMN_FLAG; + } + // buffer must have enough space for control entry + EndSegmentBlock block( + opCode, flags, txn->GetCommitSequenceNumber(), txn->GetTransactionId(), txn->GetInternalTransactionId()); redoLogBuffer.Append(block); - return true; } bool RedoLogWriter::AppendIndex(RedoLogBuffer& buffer, Table* table, Index* index) @@ -244,12 +240,20 @@ bool RedoLogWriter::AppendIndex(RedoLogBuffer& buffer, Table* table, Index* inde buffer.Append(tableId); char* serializeBuf = (char*)buffer.AllocAppend(serializeSize); MOT_ASSERT(serializeBuf != nullptr); - table->SerializeItem(serializeBuf, index); + (void)table->SerializeItem(serializeBuf, index); + MOT_LOG_TRACE("AppendIndex %s (%lu:%u) of table %s (%lu:%u), numIndexes %u", + index->GetName().c_str(), + index->GetExtId(), + index->GetIndexId(), + table->GetTableName().c_str(), + table->GetTableExId(), + table->GetTableId(), + table->GetNumIndexes()); return true; } -bool RedoLogWriter::AppendDropIndex(RedoLogBuffer& buffer, Table* table, Index* index) +bool RedoLogWriter::AppendDropIndex(RedoLogBuffer& buffer, Table* table, MOT::Index* index) { uint64_t entrySize = sizeof(OperationCode) + sizeof(uint32_t) + sizeof(uint64_t) + sizeof(size_t) + index->GetName().length() + sizeof(EndSegmentBlock); @@ -264,6 +268,15 @@ bool RedoLogWriter::AppendDropIndex(RedoLogBuffer& buffer, Table* table, Index* buffer.Append(tableId); buffer.Append(index->GetName().length()); buffer.Append(index->GetName().c_str(), index->GetName().length()); + + MOT_LOG_TRACE("AppendDropIndex %s (%lu:%u) of table %s (%lu:%u), numIndexes %u", + index->GetName().c_str(), + index->GetExtId(), + index->GetIndexId(), + table->GetTableName().c_str(), + table->GetTableExId(), + table->GetTableId(), + table->GetNumIndexes()); return true; } @@ -281,6 +294,7 @@ bool RedoLogWriter::AppendTable(RedoLogBuffer& buffer, Table* table) MOT_ASSERT(serializeBuf != nullptr); table->SerializeRedo(serializeBuf); + MOT_LOG_TRACE("AppendTable %s (%lu:%u)", table->GetTableName().c_str(), table->GetTableExId(), table->GetTableId()); return true; } @@ -296,6 +310,9 @@ bool RedoLogWriter::AppendDropTable(RedoLogBuffer& buffer, Table* table) buffer.Append(OperationCode::DROP_TABLE); buffer.Append(metaVersion); buffer.Append(tableId); + + MOT_LOG_TRACE( + "AppendDropTable %s (%lu:%u)", table->GetTableName().c_str(), table->GetTableExId(), table->GetTableId()); return true; } @@ -314,6 +331,68 @@ bool RedoLogWriter::AppendTruncateTable(RedoLogBuffer& buffer, Table* table) return true; } +bool RedoLogWriter::AppendAlterTableAddColumn(RedoLogBuffer& buffer, Table* table, Column* col) +{ + size_t serializeSize = table->SerializeItemSize(col); + uint64_t entrySize = REDO_ALTER_TABLE_FORMAT_OVERHEAD + serializeSize; + if (buffer.FreeSize() < entrySize) { + return false; + } + + uint64_t tableId = table->GetTableExId(); + uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; + buffer.Append(OperationCode::ALTER_TABLE_ADD_COLUMN); + buffer.Append(serializeSize); + buffer.Append(metaVersion); + buffer.Append(tableId); + char* serializeBuf = (char*)buffer.AllocAppend(serializeSize); + MOT_ASSERT(serializeBuf != nullptr); + (void)table->SerializeItem(serializeBuf, col); + return true; +} + +bool RedoLogWriter::AppendAlterTableDropColumn(RedoLogBuffer& buffer, Table* table, Column* col) +{ + size_t serializeSize = SerializableARR::SerializeSize(col->m_name); + uint64_t entrySize = REDO_ALTER_TABLE_FORMAT_OVERHEAD + serializeSize; + if (buffer.FreeSize() < entrySize) { + return false; + } + + uint64_t tableId = table->GetTableExId(); + uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; + buffer.Append(OperationCode::ALTER_TABLE_DROP_COLUMN); + buffer.Append(serializeSize); + buffer.Append(metaVersion); + buffer.Append(tableId); + char* serializeBuf = (char*)buffer.AllocAppend(serializeSize); + MOT_ASSERT(serializeBuf != nullptr); + (void)SerializableARR::Serialize(serializeBuf, col->m_name); + return true; +} + +bool RedoLogWriter::AppendAlterTableRenameColumn(RedoLogBuffer& buffer, Table* table, DDLAlterTableRenameColumn* alter) +{ + size_t serializeSize = SerializableARR::SerializeSize(alter->m_oldname) + + SerializableARR::SerializeSize(alter->m_newname); + uint64_t entrySize = REDO_ALTER_TABLE_FORMAT_OVERHEAD + serializeSize; + if (buffer.FreeSize() < entrySize) { + return false; + } + + uint64_t tableId = table->GetTableExId(); + uint32_t metaVersion = MetadataProtoVersion::METADATA_VER_CURR; + buffer.Append(OperationCode::ALTER_TABLE_RENAME_COLUMN); + buffer.Append(serializeSize); + buffer.Append(metaVersion); + buffer.Append(tableId); + char* serializeBuf = (char*)buffer.AllocAppend(serializeSize); + MOT_ASSERT(serializeBuf != nullptr); + serializeBuf = SerializableARR::Serialize(serializeBuf, alter->m_oldname); + (void)SerializableARR::Serialize(serializeBuf, alter->m_newname); + return true; +} + void RedoLogWriter::ApplyData(const void* data, size_t len) { OperationCode opCode = *(reinterpret_cast(data)); @@ -323,6 +402,8 @@ void RedoLogWriter::ApplyData(const void* data, size_t len) size_t EndSegmentBlockSerializer::SerializeSize(EndSegmentBlock* b) { return SerializablePOD::SerializeSize(b->m_opCode) + + SerializablePOD::SerializeSize(b->m_flags) + + SerializablePOD::SerializeSize(b->m_reserved) + SerializablePOD::SerializeSize(b->m_csn) + SerializablePOD::SerializeSize(b->m_externalTransactionId) + SerializablePOD::SerializeSize(b->m_internalTransactionId); @@ -331,6 +412,8 @@ size_t EndSegmentBlockSerializer::SerializeSize(EndSegmentBlock* b) void EndSegmentBlockSerializer::Serialize(EndSegmentBlock* b, char* dataOut) { dataOut = SerializablePOD::Serialize(dataOut, b->m_opCode); + dataOut = SerializablePOD::Serialize(dataOut, b->m_flags); + dataOut = SerializablePOD::Serialize(dataOut, b->m_reserved); dataOut = SerializablePOD::Serialize(dataOut, b->m_csn); dataOut = SerializablePOD::Serialize(dataOut, b->m_externalTransactionId); dataOut = SerializablePOD::Serialize(dataOut, b->m_internalTransactionId); @@ -340,6 +423,8 @@ void EndSegmentBlockSerializer::Deserialize(EndSegmentBlock* b, const char* in) { char* dataIn = (char*)in; dataIn = SerializablePOD::Deserialize(dataIn, b->m_opCode); + dataIn = SerializablePOD::Deserialize(dataIn, b->m_flags); + dataIn = SerializablePOD::Deserialize(dataIn, b->m_reserved); dataIn = SerializablePOD::Deserialize(dataIn, b->m_csn); dataIn = SerializablePOD::Deserialize(dataIn, b->m_externalTransactionId); dataIn = SerializablePOD::Deserialize(dataIn, b->m_internalTransactionId); diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.h b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.h index 75f43296c..55c7b2a88 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/redo_log_writer.h @@ -35,7 +35,7 @@ #include "utilities.h" #include "redo_log_buffer.h" #include "redo_statistics.h" -#include "table.h" +#include "txn_table.h" #include "bitmapset.h" namespace MOT { @@ -49,23 +49,36 @@ class Key; */ struct EndSegmentBlock { OperationCode m_opCode; + uint16_t m_flags; + uint32_t m_reserved; uint64_t m_csn; uint64_t m_externalTransactionId; uint64_t m_internalTransactionId; EndSegmentBlock() + : m_opCode(INVALID_OPERATION_CODE), + m_flags(0), + m_reserved(0), + m_csn(0), + m_externalTransactionId(INVALID_TRANSACTION_ID), + m_internalTransactionId(INVALID_TRANSACTION_ID) {} - EndSegmentBlock(OperationCode opCode, uint64_t csn, uint64_t externalTransactionId, uint64_t internalTransactionId) + EndSegmentBlock(OperationCode opCode, uint16_t flags, uint64_t csn, uint64_t externalTransactionId, + uint64_t internalTransactionId) : m_opCode(opCode), + m_flags(flags), + m_reserved(0), m_csn(csn), m_externalTransactionId(externalTransactionId), m_internalTransactionId(internalTransactionId) {} + + static constexpr uint16_t MOT_UPDATE_INDEX_COLUMN_FLAG = 0x0001; }; /** * @class EndSegmentBlockSerializer - * @brief seralizes and deserializes an end segment + * @brief serializes and deserializes an end segment * block */ class EndSegmentBlockSerializer { @@ -101,33 +114,19 @@ public: /** * @brief Appends update row redo log entry. * @param redoLogBuffer The redo buffer of the transaction. - * @param table The table identifier. - * @param primaryKey The primary key. - * @param attr The field. - * @param attrSize The field size. - * @param newValue Unused. + * @param row The updated row. + * @param modifiedColumns the row's modified columns. */ - static bool AppendUpdate(RedoLogBuffer& redoLogBuffer, uint64_t table, Key* primaryKey, uint64_t attr, - uint64_t attrSize, const void* newValue, uint64_t externalId); /** * @brief Appends update row redo log entry. * @param redoLogBuffer The redo buffer of the transaction. * @param row The row. * @param modifiedColumns Bitmap set of modified columns. + * @param version updated row's version (csn). */ - static bool AppendUpdate(RedoLogBuffer& redoLogBuffer, Row* row, BitmapSet* modifiedColumns); - - /** - * @brief Appends full row overwrite redo log entry. - * @param redoLogBuffer The redo buffer of the transaction. - * @param table The table identifier. - * @param primaryKey The primary key. - * @param rowData The row buffer. - * @param rowDataSize The row buffer size. - */ - static bool AppendOverwriteRow(RedoLogBuffer& redoLogBuffer, uint64_t table, Key* primaryKey, const void* rowData, - uint64_t rowDataSize, uint64_t externalId); + static bool AppendUpdate( + RedoLogBuffer& redoLogBuffer, Row* row, BitmapSet* modifiedColumns, uint64_t previous_version, bool idxUpd); /** * @brief Appends New row redo log entry. @@ -144,45 +143,45 @@ public: * @param table The table identifier. * @param primaryKey The primary key. * @param external table id. - * @param row version. + * @param version The deleted row version. */ - static bool AppendRemove(RedoLogBuffer& redoLogBuffer, uint64_t table, const Key* primaryKey, uint64_t externalId, - uint64_t version); + static bool AppendRemove( + RedoLogBuffer& redoLogBuffer, uint64_t table, const Key* primaryKey, uint64_t version, uint64_t externalId); /** * @brief Appends a commit transaction redo log entry. * @param redoLogBuffer The redo buffer of the transaction. */ - static bool AppendCommit(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendCommit(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * @brief Appends a commit transaction redo log entry. * @param redoLogBuffer The redo buffer of the transaction. */ - static bool AppendPrepare(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendPrepare(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * @brief Appends a commit transaction redo log entry. * @param redoLogBuffer The redo buffer of the transaction. */ - static bool AppendCommitPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendCommitPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * @brief Appends a commit transaction redo log entry. * @param redoLogBuffer The redo buffer of the transaction. */ - static bool AppendRollbackPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendRollbackPrepared(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * @brief Appends a rollback transaction redo log entry. * @param redoLogBuffer The redo buffer of the transaction. */ - static bool AppendRollback(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendRollback(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * @brief Appends a partial redo log control entry. */ - static bool AppendPartial(RedoLogBuffer& redoLogBuffer, TxnManager* txn); + static void AppendPartial(RedoLogBuffer& redoLogBuffer, TxnManager* txn); /** * Adds a table to the log. @@ -203,7 +202,7 @@ public: * @param buffer The buffer that the index will be serialized to. * @param len The data buffer length. */ - static bool AppendIndex(RedoLogBuffer& buffer, Table* table, Index* index); + static bool AppendIndex(RedoLogBuffer& buffer, Table* table, MOT::Index* index); /** * Adds a drop index entry to the log. @@ -211,7 +210,7 @@ public: * @param table The table the index belongs to. * @param index The index to drop. */ - static bool AppendDropIndex(RedoLogBuffer& buffer, Table* table, Index* index); + static bool AppendDropIndex(RedoLogBuffer& buffer, Table* table, MOT::Index* index); /** * Adds a truncate table entry to the log. @@ -221,6 +220,30 @@ public: */ static bool AppendTruncateTable(RedoLogBuffer& buffer, Table* table); + /** + * Adds a alter table add column entry to the log. + * @param buffer The buffer that the index will be serialized to. + * @param table The table to alter. + * @param col the added column. + */ + static bool AppendAlterTableAddColumn(RedoLogBuffer& buffer, Table* table, Column* col); + + /** + * Adds a alter table drop column entry to the log. + * @param buffer The buffer that the index will be serialized to. + * @param table The table to alter. + * @param col the dropped column. + */ + static bool AppendAlterTableDropColumn(RedoLogBuffer& buffer, Table* table, Column* col); + + /** + * Adds a alter table rename column entry to the log. + * @param buffer The buffer that the index will be serialized to. + * @param table The table to alter. + * @param alter Structure that contains table and rename. + */ + static bool AppendAlterTableRenameColumn(RedoLogBuffer& buffer, Table* table, DDLAlterTableRenameColumn* alter); + /** * Prints the operation code of a data buffer. * @param data The data buffer. @@ -238,11 +261,15 @@ public: /* @def Create Index redo format overhead. */ static constexpr uint32_t REDO_CREATE_INDEX_FORMAT_OVERHEAD = - sizeof(OperationCode) + sizeof(size_t) + sizeof(uint64_t) + sizeof(uint32_t) + sizeof(EndSegmentBlock); + sizeof(OperationCode) + sizeof(size_t) + sizeof(uint32_t) + sizeof(uint64_t) + sizeof(EndSegmentBlock); /* @def Max limit of index serialize size. */ static constexpr uint32_t REDO_MAX_INDEX_SERIALIZE_SIZE = RedoLogBuffer::REDO_DEFAULT_BUFFER_SIZE - REDO_CREATE_INDEX_FORMAT_OVERHEAD; + + /* @def Alter Table redo format overhead. */ + static constexpr uint32_t REDO_ALTER_TABLE_FORMAT_OVERHEAD = + sizeof(OperationCode) + sizeof(size_t) + sizeof(uint32_t) + sizeof(uint64_t) + sizeof(EndSegmentBlock); }; } // End of namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.cpp b/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.cpp similarity index 63% rename from src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.cpp rename to src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.cpp index 4b2f2a052..0f0f258be 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.cpp @@ -13,54 +13,57 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * synchronous_redo_log_handler.cpp + * sync_redo_log_handler.cpp * Implements a synchronous redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * synchronous_redo_log/synchronous_redo_log_handler.cpp + * synchronous_redo_log/sync_redo_log_handler.cpp * * ------------------------------------------------------------------------- */ -#include "synchronous_redo_log_handler.h" +#include "sync_redo_log_handler.h" #include "txn.h" #include "global.h" #include "utilities.h" namespace MOT { -SynchronousRedoLogHandler::SynchronousRedoLogHandler() +SyncRedoLogHandler::SyncRedoLogHandler() {} -SynchronousRedoLogHandler::~SynchronousRedoLogHandler() +SyncRedoLogHandler::~SyncRedoLogHandler() {} -RedoLogBuffer* SynchronousRedoLogHandler::CreateBuffer() +RedoLogBuffer* SyncRedoLogHandler::CreateBuffer() { RedoLogBuffer* buffer = new (std::nothrow) RedoLogBuffer(); - if (buffer != nullptr) { - if (!buffer->Initialize()) { - delete buffer; - buffer = nullptr; - } + if (buffer == nullptr) { + return nullptr; } + + if (!buffer->Initialize()) { + delete buffer; + return nullptr; + } + return buffer; } -void SynchronousRedoLogHandler::DestroyBuffer(RedoLogBuffer* buffer) +void SyncRedoLogHandler::DestroyBuffer(RedoLogBuffer* buffer) { if (buffer != nullptr) { delete buffer; } } -RedoLogBuffer* SynchronousRedoLogHandler::WriteToLog(RedoLogBuffer* buffer) +RedoLogBuffer* SyncRedoLogHandler::WriteToLog(RedoLogBuffer* buffer) { - m_logger->AddToLog(buffer); + (void)m_logger->AddToLog(buffer); m_logger->FlushLog(); return buffer; } -void SynchronousRedoLogHandler::Flush() +void SyncRedoLogHandler::Flush() {} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.h b/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.h similarity index 73% rename from src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.h rename to src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.h index 8c74e4048..aa7a9fdd8 100644 --- a/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/synchronous_redo_log_handler.h +++ b/src/gausskernel/storage/mot/core/system/transaction_logger/synchronous_redo_log/sync_redo_log_handler.h @@ -13,18 +13,18 @@ * See the Mulan PSL v2 for more details. * ------------------------------------------------------------------------- * - * synchronous_redo_log_handler.h + * sync_redo_log_handler.h * Implements a synchronous redo log. * * IDENTIFICATION * src/gausskernel/storage/mot/core/system/transaction_logger/ - * synchronous_redo_log/synchronous_redo_log_handler.h + * synchronous_redo_log/sync_redo_log_handler.h * * ------------------------------------------------------------------------- */ -#ifndef SYNCHRONOUS_REDO_LOG_HANDLER_H -#define SYNCHRONOUS_REDO_LOG_HANDLER_H +#ifndef SYNC_REDO_LOG_HANDLER_H +#define SYNC_REDO_LOG_HANDLER_H #include "redo_log_handler.h" @@ -32,12 +32,12 @@ namespace MOT { class TxnManager; /** - * @class SynchronousRedoLogHandler + * @class SyncRedoLogHandler * @brief implements a synchronous redo log */ -class SynchronousRedoLogHandler : public RedoLogHandler { +class SyncRedoLogHandler : public RedoLogHandler { public: - SynchronousRedoLogHandler(); + SyncRedoLogHandler(); /** * @brief creates a new Buffer object @@ -58,10 +58,10 @@ public: */ RedoLogBuffer* WriteToLog(RedoLogBuffer* buffer); void Flush(); - SynchronousRedoLogHandler(const SynchronousRedoLogHandler& orig) = delete; - SynchronousRedoLogHandler& operator=(const SynchronousRedoLogHandler& orig) = delete; - ~SynchronousRedoLogHandler(); + SyncRedoLogHandler(const SyncRedoLogHandler& orig) = delete; + SyncRedoLogHandler& operator=(const SyncRedoLogHandler& orig) = delete; + ~SyncRedoLogHandler(); }; } // namespace MOT -#endif /* SYNCHRONOUS_REDO_LOG_HANDLER_H */ +#endif /* SYNC_REDO_LOG_HANDLER_H */ diff --git a/src/gausskernel/storage/mot/core/utils/buffer.h b/src/gausskernel/storage/mot/core/utils/buffer.h index b544ec719..660dd67cc 100644 --- a/src/gausskernel/storage/mot/core/utils/buffer.h +++ b/src/gausskernel/storage/mot/core/utils/buffer.h @@ -25,7 +25,7 @@ #ifndef MOT_BUFFER_H #define MOT_BUFFER_H -#define DEFAULT_BUFFER_SIZE 4096 * 1000 // 4MB +#define DEFAULT_BUFFER_SIZE (4 * 1024 * 1024) // 4MB #include #include @@ -42,16 +42,21 @@ public: : m_bufferSize(size), m_nextFree(0), m_buffer(buffer), m_allocated(true) {} - inline Buffer(uint32_t size) : m_bufferSize(size), m_nextFree(0), m_allocated(false) + inline Buffer(uint32_t size) : m_bufferSize(size), m_nextFree(0), m_buffer(nullptr), m_allocated(false) {} - inline virtual ~Buffer() + ~Buffer() { if (m_buffer) { delete[] m_buffer; } } + inline bool IsAllocated() const + { + return m_allocated; + } + inline bool Initialize() { if (!m_allocated) { @@ -123,43 +128,15 @@ public: } } - /** - * @brief Appends raw data from input stream. - * @param is The input stream - * @param size The amount of bytes to append from the stream. - */ - bool Append(std::istream& is, uint32_t size) - { - if (m_nextFree + size <= m_bufferSize) { - char* ptr = (char*)&m_buffer[m_nextFree]; - is.read(ptr, size); - m_nextFree += size; - return true; - } else { - return false; - } - } - /** * @brief Retrieves the data of the buffer. * @return The buffer. */ - inline void* Data() const + inline const void* Data() const { return m_buffer; } - /** - * @brief Retrieves the data of the buffer. - * @param[out] size of the buffer. - * @return The buffer. - */ - inline void* Data(uint32_t* size) const - { - *size = m_nextFree; - return m_buffer; - } - /** @brief Resets the buffer. */ inline void Reset() { diff --git a/src/gausskernel/storage/mot/core/utils/debug_utils.cpp b/src/gausskernel/storage/mot/core/utils/debug_utils.cpp index 4a7a457fc..ba110eac9 100644 --- a/src/gausskernel/storage/mot/core/utils/debug_utils.cpp +++ b/src/gausskernel/storage/mot/core/utils/debug_utils.cpp @@ -71,22 +71,22 @@ extern bool InitializeDebugUtils() extern void DestroyDebugUtils() { if (initialized) { - pthread_mutex_destroy(&abortLock); + (void)pthread_mutex_destroy(&abortLock); initialized = false; } } -extern void MOTAbort(void* faultAddress /* = NULL */) +extern void MOTAbort(void* faultAddress /* = nullptr */) { - pthread_mutex_lock(&abortLock); + (void)pthread_mutex_lock(&abortLock); if (!abortFlag) { abortFlag = true; MOTDumpCallStack(); // print whatever the application wanted to print - fflush(stderr); - fflush(stdout); + (void)fflush(stderr); + (void)fflush(stdout); // analyze fault address if (faultAddress != nullptr) { @@ -99,7 +99,7 @@ extern void MOTAbort(void* faultAddress /* = NULL */) // now call abort ::abort(); } - pthread_mutex_unlock(&abortLock); // just for proper order + (void)pthread_mutex_unlock(&abortLock); // just for proper order } static void SetCallStackName(char* name, const char* sourceName, size_t size) @@ -136,7 +136,7 @@ static void ParseCallStackFrame(void* frame, char* frameStr, CallStackFrame* res // parse frame and offset char* closeParenPos = strchr(frameStr, ')'); if (closeParenPos != nullptr) { - size = closeParenPos - openParenPos - 1; + size = (closeParenPos - openParenPos) - 1; SetCallStackName(resolvedFrame->m_frame, openParenPos + 1, size); // now break it again into frame and offset @@ -158,22 +158,28 @@ static void ParseCallStackFrame(void* frame, char* frameStr, CallStackFrame* res if (opts & MOT_CALL_STACK_DEMANGLE) { if (resolvedFrame->m_frame[0] != 0) { mot_string sysCom; - sysCom.format("echo '%s' | c++filt", resolvedFrame->m_frame); - std::string sysRes = ExecOsCommand(sysCom.c_str()); - SetCallStackName(resolvedFrame->m_demangledFrame, sysRes.c_str(), sysRes.length()); + if (!sysCom.format("echo '%s' | c++filt", resolvedFrame->m_frame)) { + MOT_LOG_TRACE("Failed to format de-mangle command line"); + } else { + std::string sysRes = ExecOsCommand(sysCom.c_str()); + SetCallStackName(resolvedFrame->m_demangledFrame, sysRes.c_str(), sysRes.length()); + } } } // 4. get file and line if required if (opts & MOT_CALL_STACK_FILE_LINE) { mot_string sysCom; - sysCom.format("addr2line %p -e %s", frame, resolvedFrame->m_module); - std::string sysRes = ExecOsCommand(sysCom.c_str()); - SetCallStackName(resolvedFrame->m_file, sysRes.c_str(), sysRes.length()); - char* colonPos = strchr(resolvedFrame->m_file, ':'); - if (colonPos != nullptr) { - resolvedFrame->m_line = strtoull(colonPos + 1, nullptr, 0); - *colonPos = 0; + if (!sysCom.format("addr2line %p -e %s", frame, resolvedFrame->m_module)) { + MOT_LOG_TRACE("Failed to format address-to-line command line"); + } else { + std::string sysRes = ExecOsCommand(sysCom.c_str()); + SetCallStackName(resolvedFrame->m_file, sysRes.c_str(), sysRes.length()); + char* colonPos = strchr(resolvedFrame->m_file, ':'); + if (colonPos != nullptr) { + resolvedFrame->m_line = strtoull(colonPos + 1, nullptr, 0); + *colonPos = 0; + } } } } @@ -224,7 +230,7 @@ extern void MOTPrintCallStackImplV(LogLevel logLevel, const char* logger, int op // format message StringBuffer sb = {0}; - StringBufferInit(&sb, 1024, 2, MOT::StringBuffer::MULTIPLY); + StringBufferInit(&sb, 1024, MOT::StringBuffer::GROWTH_FACTOR, MOT::StringBuffer::MULTIPLY); StringBufferAppendV(&sb, format, argsCopy); StringBufferAppend(&sb, "\n"); for (int i = 0; i < frameCount; ++i) { diff --git a/src/gausskernel/storage/mot/core/utils/debug_utils.h b/src/gausskernel/storage/mot/core/utils/debug_utils.h index fa0cf51ab..3e30001ed 100644 --- a/src/gausskernel/storage/mot/core/utils/debug_utils.h +++ b/src/gausskernel/storage/mot/core/utils/debug_utils.h @@ -25,9 +25,9 @@ #ifndef DEBUG_UTILS_H #define DEBUG_UTILS_H -#include -#include -#include +#include +#include +#include #include "log_level.h" diff --git a/src/gausskernel/storage/mot/core/utils/log_level.cpp b/src/gausskernel/storage/mot/core/utils/log_level.cpp index 7e0a39ea1..6866898cc 100644 --- a/src/gausskernel/storage/mot/core/utils/log_level.cpp +++ b/src/gausskernel/storage/mot/core/utils/log_level.cpp @@ -26,14 +26,14 @@ #include "logger.h" #include "mot_log.h" -#include +#include namespace MOT { DECLARE_LOGGER(LogLevel, Utilities) extern LogLevel LogLevelFromString(const char* logLevelStr) { - LogLevel logLevel = LogLevel::LL_INFO; // default value if parsing fails + LogLevel logLevel = LogLevel::LL_INVALID; MOT_LOG_DEBUG("Parsing log level: %s", logLevelStr); if (strcmp(logLevelStr, "PANIC") == 0) { @@ -89,4 +89,12 @@ extern const char* LogLevelToString(LogLevel logLevel) return "INVALID"; } } + +extern bool ValidateLogLevel(const char* logLevelStr) +{ + if (LogLevelFromString(logLevelStr) == LogLevel::LL_INVALID) { + return false; + } + return true; +} } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/utils/log_level.h b/src/gausskernel/storage/mot/core/utils/log_level.h index 62f637496..0f0f8bb51 100644 --- a/src/gausskernel/storage/mot/core/utils/log_level.h +++ b/src/gausskernel/storage/mot/core/utils/log_level.h @@ -26,7 +26,7 @@ #define LOG_LEVEL_H #include -#include +#include namespace MOT { /** @@ -66,6 +66,9 @@ extern LogLevel LogLevelFromString(const char* logLevelStr); /** @brief Converts log level constant to string. */ extern const char* LogLevelToString(LogLevel logLevel); + +/** @brief Validates the log level string. */ +extern bool ValidateLogLevel(const char* logLevelStr); } // namespace MOT #endif /* LOG_LEVEL_H */ diff --git a/src/gausskernel/storage/mot/core/utils/logger.cpp b/src/gausskernel/storage/mot/core/utils/logger.cpp index c453b8feb..9e57e8cbb 100644 --- a/src/gausskernel/storage/mot/core/utils/logger.cpp +++ b/src/gausskernel/storage/mot/core/utils/logger.cpp @@ -82,14 +82,42 @@ static void RegisterLogger(Logger* logger) if (itr == loggerMap->end()) { LoggerMap::pairis pairis = loggerMap->insert( LoggerMap::value_type(logger->m_componentName, mot_make_pair(LogLevel::LL_INFO, LoggerList()))); - MOT_ASSERT(pairis.second == INSERT_SUCCESS); + switch (pairis.second) { + case INSERT_SUCCESS: + break; + case INSERT_FAILED: + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "System Startup", + "Failed to insert component %s for logger %s to loggerMap", + logger->m_componentName, + logger->m_loggerName); + return; + case INSERT_EXISTS: + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "System Startup", + "Failed to insert component %s for logger %s to loggerMap. Element already exists", + logger->m_componentName, + logger->m_loggerName); + return; + default: + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "System Startup", + "Unknown error code %d while inserting component %s logger %s to loggerMap", + pairis.second, + logger->m_componentName, + logger->m_loggerName); + return; + } + itr = pairis.first; MOT_LOG_DEBUG("Registered log component: %s", logger->m_componentName); } LoggerList& loggerList = itr->second.second; LoggerList::iterator itr2 = find(loggerList.begin(), loggerList.end(), logger); MOT_ASSERT(itr2 == loggerList.end()); - loggerList.push_back(logger); + if (!loggerList.push_back(logger)) { + MOT_LOG_ERROR("Failed to add log %s to logger's list", logger->m_loggerName); + } } /** @@ -107,10 +135,10 @@ static void UnregisterLogger(Logger* logger) LoggerList& loggerList = itr->second.second; LoggerList::iterator itr2 = find(loggerList.begin(), loggerList.end(), logger); MOT_ASSERT(itr2 != loggerList.end()); - loggerList.erase(itr2); + (void)loggerList.erase(itr2); if (loggerList.empty()) { MOT_LOG_DEBUG("Unregistered log component: %s", logger->m_componentName); - loggerMap->erase(itr); + (void)loggerMap->erase(itr); if (loggerMap->empty()) { delete loggerMap; loggerMap = nullptr; diff --git a/src/gausskernel/storage/mot/core/utils/mot_log.cpp b/src/gausskernel/storage/mot/core/utils/mot_log.cpp index 6561cfcdd..3cb90bd2f 100644 --- a/src/gausskernel/storage/mot/core/utils/mot_log.cpp +++ b/src/gausskernel/storage/mot/core/utils/mot_log.cpp @@ -28,10 +28,10 @@ #include "session_context.h" #include "mm_api.h" -#include +#include #include -#include -#include +#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -141,7 +141,7 @@ static void MOTWriteToFileSink(const char* line, size_t size, void* userData) { FILE* fileSink = (FILE*)userData; if (fileSink != nullptr) { - fwrite(line, 1, size, fileSink); + (void)fwrite(line, 1, size, fileSink); } } @@ -149,7 +149,7 @@ static void MOTFormatToFileSink(const char* format, va_list args, void* userData { FILE* fileSink = (FILE*)userData; if (fileSink != nullptr) { - vfprintf(fileSink, format, args); + (void)vfprintf(fileSink, format, args); } } @@ -157,7 +157,7 @@ static void MOTFlushFileSink(void* userData) { FILE* fileSink = (FILE*)userData; if (fileSink != nullptr) { - fflush(fileSink); + (void)fflush(fileSink); } } @@ -187,7 +187,7 @@ extern void SetLogSink(MotLogSinkType logSinkType, MOTLogSink* logSink /* = null break; default: - fprintf(stderr, + (void)fprintf(stderr, "[MOT Logger Error] Request to configure log sink denied: Invalid logger sink type %d", (int)logSinkType); break; @@ -285,7 +285,8 @@ static inline void MOTAppendLogLineV(const char* format, va_list args) #elif defined(ENABLE_LONG_LOG_LINE) && (LONG_LOG_LINE_MODE == LONG_LOG_LINE_STRING_BUFFER) // log data is too long for log line, so we create an emergency buffer LOG_LINE_BUF = (StringBuffer*)malloc(sizeof(StringBuffer)); - StringBufferInit(LOG_LINE_BUF, MOT_MAX_LOG_LINE_LENGTH, 2, StringBuffer::Multiply); + StringBufferInit( + LOG_LINE_BUF, MOT_MAX_LOG_LINE_LENGTH, StringBuffer::GROWTH_FACTOR, StringBuffer::MULTIPLY); // copy what was formatted up until this call, and format on string buffer StringBufferAppendN(LOG_LINE_BUF, LOG_LINE, LOG_LINE_POS); @@ -319,7 +320,7 @@ static inline void MOTEndLogLine() #elif defined(ENABLE_LONG_LOG_LINE) && (LONG_LOG_LINE_MODE == LONG_LOG_LINE_STRING_BUFFER) // if we overflowed then we print the string buffer and cleanup StringBufferAppend(LOG_LINE_BUF, "\n"); - MOTWriteToLogSink(LOG_LINE_BUF->_buffer, logLineBuf->m_pos); + MOTWriteToLogSink(LOG_LINE_BUF->m_buffer, LOG_LINE_BUF->m_pos); StringBufferDestroy(LOG_LINE_BUF); LOG_LINE_BUF = nullptr; #else @@ -344,7 +345,7 @@ static inline const char* MOTFormatLogTime(char* buffer, size_t len) struct tm* tmInfo; struct timeval tv; struct tm localTime; - gettimeofday(&tv, NULL); + (void)gettimeofday(&tv, nullptr); long int millisec = lrint(tv.tv_usec / 1000.0); // Round to nearest millisec if (millisec >= 1000) { // Allow for rounding up to nearest second @@ -356,7 +357,7 @@ static inline const char* MOTFormatLogTime(char* buffer, size_t len) // format time size_t offset = strftime(buffer, len, "%Y-%m-%d %H:%M:%S", tmInfo); - errno_t erc = snprintf_s(buffer + offset, len - offset, len - offset - 1, ".%03d", (int)millisec); + errno_t erc = snprintf_s(buffer + offset, len - offset, (len - offset) - 1, ".%03d", (int)millisec); securec_check_ss(erc, "\0", "\0"); return buffer; } @@ -445,6 +446,11 @@ extern void MOTLogAppend(const char* format, ...) va_end(args); } +extern void MOTLogAppendV(const char* format, va_list args) +{ + MOTAppendLogLineV(format, args); +} + extern void MOTLogEnd() { MOTEndLogLine(); diff --git a/src/gausskernel/storage/mot/core/utils/mot_log.h b/src/gausskernel/storage/mot/core/utils/mot_log.h index 04d663fb9..132ff9e88 100644 --- a/src/gausskernel/storage/mot/core/utils/mot_log.h +++ b/src/gausskernel/storage/mot/core/utils/mot_log.h @@ -27,9 +27,9 @@ #include "log_level.h" -#include -#include -#include +#include +#include +#include /** * @define LOGGER_INDENT_SIZE The number bytes required to maintain indentation in multi-line log @@ -154,6 +154,11 @@ extern void MOTLogBegin(LogLevel logLevel, const char* loggerName, const char* f **/ extern void MOTLogAppend(const char* format, ...); +/** + * @brief Appends a message to a continued printing sequence. + **/ +extern void MOTLogAppendV(const char* format, va_list args); + /** * @brief Terminates a continued printing sequence. */ @@ -201,6 +206,18 @@ extern void MOTPrintCallStack(LogLevel logLevel, const char* loggerName, int opt /** @define Utility macro for checking whether the log level of the current logger is sufficient for printing. */ #define MOT_CHECK_LOG_LEVEL(logLevel) MOT::CheckLogLevelInline(logLevel, MOT_LOGGER_LEVEL) +/** @define Utility macro for checking whether the current logger can print TRACE-level messages. */ +#define MOT_CHECK_TRACE_LOG_LEVEL() MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE) + +/** @define Utility macro for checking whether the current logger can print DEBUG-level messages. */ +#define MOT_CHECK_DEBUG_LOG_LEVEL() MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG) + +/** @define Utility macro for checking whether the current logger can print DIAG1-level messages. */ +#define MOT_CHECK_DIAG1_LOG_LEVEL() MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DIAG1) + +/** @define Utility macro for checking whether the current logger can print DIAG2-level messages. */ +#define MOT_CHECK_DIAG2_LOG_LEVEL() MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DIAG2) + /** @define Begins a log printing in continuation. */ #define MOT_LOG_BEGIN(logLevel, format, ...) \ if (MOT_CHECK_LOG_LEVEL(logLevel)) { \ @@ -213,6 +230,12 @@ extern void MOTPrintCallStack(LogLevel logLevel, const char* loggerName, int opt MOT::MOTLogAppend(format, ##__VA_ARGS__); \ } +/** @define Adds a log message to log printing in continuation. */ +#define MOT_LOG_APPEND_V(logLevel, format, args) \ + if (MOT_CHECK_LOG_LEVEL(logLevel)) { \ + MOT::MOTLogAppendV(format, args); \ + } + /** @define Ends a log printing in continuation. */ #define MOT_LOG_END(logLevel) \ if (MOT_CHECK_LOG_LEVEL(logLevel)) { \ diff --git a/src/gausskernel/storage/mot/core/utils/serializable.h b/src/gausskernel/storage/mot/core/utils/serializable.h index 5eabc8226..36c687b0b 100644 --- a/src/gausskernel/storage/mot/core/utils/serializable.h +++ b/src/gausskernel/storage/mot/core/utils/serializable.h @@ -63,12 +63,12 @@ public: // String serialization class SerializableSTR { public: - static size_t SerializeSize(string str) + static size_t SerializeSize(const string& str) { return str.length() + sizeof(size_t); } - static char* Serialize(char* target, string value) + static char* Serialize(char* target, const string& value) { size_t length = value.length(); errno_t erc = memcpy_s(target, sizeof(size_t), &length, sizeof(size_t)); @@ -83,7 +83,7 @@ public: size_t length; errno_t erc = memcpy_s(&length, sizeof(size_t), source, sizeof(size_t)); securec_check(erc, "\0", "\0"); - target.assign(source + sizeof(size_t), length); + (void)target.assign(source + sizeof(size_t), length); return source + sizeof(size_t) + length; } }; @@ -105,6 +105,12 @@ public: return target + sizeof(size_t) + len; } + static void DeserializeSize(char* source, size_t& len) + { + errno_t erc = memcpy_s(&len, sizeof(size_t), source, sizeof(size_t)); + securec_check(erc, "\0", "\0"); + } + static char* Deserialize(char* source, char* target, size_t target_len) { errno_t erc = memcpy_s(target, target_len, source + sizeof(size_t), target_len); diff --git a/src/gausskernel/storage/mot/core/utils/string_buffer.cpp b/src/gausskernel/storage/mot/core/utils/string_buffer.cpp index 1823b171b..bea5c298f 100644 --- a/src/gausskernel/storage/mot/core/utils/string_buffer.cpp +++ b/src/gausskernel/storage/mot/core/utils/string_buffer.cpp @@ -26,9 +26,9 @@ #include "string_buffer.h" #include "debug_utils.h" -#include -#include -#include +#include +#include +#include namespace MOT { static bool StringBufferRealloc(StringBuffer* stringBuffer, int sppendSize); @@ -72,7 +72,7 @@ extern void StringBufferAppendV(StringBuffer* stringBuffer, const char* format, va_copy(argsCopy, args); // must reaffirm after previous call to vsnprintf()! errno_t erc = vsnprintf_s(stringBuffer->m_buffer + stringBuffer->m_pos, stringBuffer->m_length - stringBuffer->m_pos, - stringBuffer->m_length - stringBuffer->m_pos - 1, + (stringBuffer->m_length - stringBuffer->m_pos) - 1, format, argsCopy); securec_check_ss(erc, "\0", "\0"); @@ -117,7 +117,7 @@ static bool StringBufferRealloc(StringBuffer* stringBuffer, int sppendSize) int requiredSize = stringBuffer->m_pos + sppendSize; if (stringBuffer->m_growMethod == StringBuffer::ADD) { newLength = - (requiredSize + stringBuffer->m_growFactor - 1) / stringBuffer->m_growFactor * stringBuffer->m_growFactor; + ((requiredSize + stringBuffer->m_growFactor - 1) / stringBuffer->m_growFactor) * stringBuffer->m_growFactor; } else { // multiply while (newLength <= requiredSize) { newLength *= stringBuffer->m_growFactor; diff --git a/src/gausskernel/storage/mot/core/utils/string_buffer.h b/src/gausskernel/storage/mot/core/utils/string_buffer.h index 6c8b5b64e..4c2c65166 100644 --- a/src/gausskernel/storage/mot/core/utils/string_buffer.h +++ b/src/gausskernel/storage/mot/core/utils/string_buffer.h @@ -25,9 +25,9 @@ #ifndef STRING_BUFFER_H #define STRING_BUFFER_H -#include -#include -#include +#include +#include +#include namespace MOT { /** @@ -58,6 +58,8 @@ struct StringBuffer { /** @var Buffer size is fixed. */ FIXED } m_growMethod; + + static constexpr uint32_t GROWTH_FACTOR = 2; }; /** @@ -152,10 +154,10 @@ extern void StringBufferAppendN(StringBuffer* stringBuffer, const char* str, int * @param[opt] initialSize The initial string buffer size. */ template -inline void StringBufferApply(Functor functor, uint32_t initialSize = 1024) +inline void StringBufferApply(const Functor& functor, uint32_t initialSize = 1024) { StringBuffer sb = {0}; - StringBufferInit(&sb, initialSize, 2, MOT::StringBuffer::MULTIPLY); + StringBufferInit(&sb, initialSize, StringBuffer::GROWTH_FACTOR, MOT::StringBuffer::MULTIPLY); functor(&sb); StringBufferDestroy(&sb); } @@ -169,7 +171,7 @@ inline void StringBufferApply(Functor functor, uint32_t initialSize = 1024) * @param length The fixed string buffer length. */ template -inline void StringBufferApplyFixed(Functor functor, char* buffer, uint32_t length) +inline void StringBufferApplyFixed(const Functor& functor, char* buffer, uint32_t length) { StringBuffer sb = {0}; StringBufferInitFixed(&sb, buffer, length); diff --git a/src/gausskernel/storage/mot/core/utils/utilities.cpp b/src/gausskernel/storage/mot/core/utils/utilities.cpp index 6b6ba3908..cb6e60e15 100644 --- a/src/gausskernel/storage/mot/core/utils/utilities.cpp +++ b/src/gausskernel/storage/mot/core/utils/utilities.cpp @@ -23,7 +23,7 @@ */ #include -#include +#include #include "sys_numa_api.h" #include #include @@ -57,14 +57,17 @@ std::string ExecOsCommand(const string& cmd) std::string ExecOsCommand(const char* cmd) { - char buffer[128]; + const int bufSize = 128; + char buffer[bufSize]; std::string result; std::shared_ptr pipe(popen(cmd, "r"), pclose); - if (!pipe) + if (!pipe) { return nullptr; + } while (!feof(pipe.get())) { - if (fgets(buffer, 128, pipe.get()) != nullptr) + if (fgets(buffer, bufSize, pipe.get()) != nullptr) { result += buffer; + } } return result; } diff --git a/src/gausskernel/storage/mot/core/utils/utilities.h b/src/gausskernel/storage/mot/core/utils/utilities.h index 7d293c735..344da19dd 100644 --- a/src/gausskernel/storage/mot/core/utils/utilities.h +++ b/src/gausskernel/storage/mot/core/utils/utilities.h @@ -34,17 +34,20 @@ #include "debug_utils.h" namespace MOT { +#define MAX_EXPONENT 63 // 2^63 is the maximum power of 2 in uint64_t +#define POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0) + /** - * @brief Executes system command (opens a pipe to a sub-preocess). + * @brief Executes system command (opens a pipe to a sub-process). * @param cmd The command to execute. - * @return The commadn output. + * @return The command output. */ std::string ExecOsCommand(const std::string& cmd); /** - * @brief Executes system command (opens a pipe to a sub-preocess). + * @brief Executes system command (opens a pipe to a sub-process). * @param cmd The command to execute. - * @return The commadn output. + * @return The command output. */ std::string ExecOsCommand(const char* cmd); @@ -56,6 +59,53 @@ std::string ExecOsCommand(const char* cmd); */ std::string HexStr(const uint8_t* data, uint16_t len); +/** + * @brief Computes the nearest power of 2 lower than the given value. + * @param value Value for which to calculate the nearest power of 2. + * @return Nearest power of 2 value. + */ +inline uint64_t ComputeNearestLowPow2(uint64_t value) +{ + if (value == 0) { + return 1; + } + + if (POWER_OF_TWO(value)) { + return value; + } + + // Count the number zero bits in the left, compute the power and return the result. + uint64_t zeroCount = __builtin_clzll(value); + uint64_t powValue = (MAX_EXPONENT - zeroCount); + uint64_t result = ((uint64_t)1) << powValue; + return result; +} + +/** + * @brief Computes the nearest power of 2 higher than the given value. + * @param value Value for which to calculate the nearest power of 2. + * @return Nearest power of 2 value. + */ +inline uint64_t ComputeNearestHighPow2(uint64_t value) +{ + if (value == 0) { + return 1; + } + + if (POWER_OF_TWO(value)) { + return value; + } + + // Count the number zero bits in the left, compute the power and return the result. + uint64_t zeroCount = __builtin_clzll(value); + uint64_t powValue = ((MAX_EXPONENT - zeroCount) + 1); + if (powValue > MAX_EXPONENT) { + powValue = MAX_EXPONENT; + } + uint64_t result = ((uint64_t)1) << powValue; + return result; +} + /** @define Likely execution path to assist compiler optimizations. */ #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) @@ -80,6 +130,16 @@ std::string HexStr(const uint8_t* data, uint16_t len); /** @define Compile-time conversion of identifier to string literal. */ #define stringify(name) #name + +template +void SetNullIfPtr(T obj) +{} + +template +void SetNullIfPtr(T*& ptr) +{ + ptr = nullptr; +} } // namespace MOT #endif // UTILITIES_H diff --git a/src/gausskernel/storage/mot/fdw_adapter/Makefile b/src/gausskernel/storage/mot/fdw_adapter/Makefile index 42b20d804..17492eee0 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/Makefile +++ b/src/gausskernel/storage/mot/fdw_adapter/Makefile @@ -28,7 +28,6 @@ EXTENSION = mot_fdw REGRESS = mot_fdw EXTRA_CLEAN = sql/mot_fdw.sql expected/mot_fdw.out -DATA = mot_fdw--1.0.sql mot_fdw.control subdir=src/gausskernel/storage/mot/fdw_adapter top_builddir ?= ../../../../../ @@ -38,19 +37,24 @@ ENGINE_INC = $(top_builddir)/src/gausskernel/storage/mot/core include $(top_builddir)/src/Makefile.global OBJ_DIR = ./obj -OBJS = $(OBJ_DIR)/mot_fdw.o $(OBJ_DIR)/mot_internal.o $(OBJ_DIR)/mot_fdw_xlog.o $(OBJ_DIR)/mot_match_index.o $(OBJ_DIR)/mot_fdw_error.o + +SRCS := $(wildcard *.cpp) +OBJS := $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(SRCS)) +DEPS := $(patsubst %.cpp, $(OBJ_DIR)/%.d, $(SRCS)) DATA = mot_fdw.control mot_fdw--1.0.sql -DEPS := $(OBJ_DIR)/mot_fdw.d $(OBJ_DIR)/mot_internal.d $(OBJ_DIR)/mot_fdx_xlog.d $(OBJ_DIR)/mot_match_index.d $(OBJ_DIR)/mot_fdw_error.d - # Shared library stuff include $(top_srcdir)/src/gausskernel/common.mk override CXXFLAGS += -DMOT_SECURE -I$(top_builddir)/src/gausskernel/storage/mot/jit_exec -I$(top_builddir)/src/gausskernel/storage/mot/fdw_adapter -I$(ENGINE_INC) -I$(ENGINE_INC)/storage -I$(ENGINE_INC)/system -I$(ENGINE_INC)/memory -I$(ENGINE_INC)/memory/garbage_collector -override CXXFLAGS += -I$(ENGINE_INC)/infra -I$(ENGINE_INC)/infra/config -I$(ENGINE_INC)/infra/containers -I$(ENGINE_INC)/infra/stats -I$(ENGINE_INC)/infra/synchronization -I$(ENGINE_INC)/concurrency_control -I$(ENGINE_INC)/storage/index -I$(ENGINE_INC)/system/transaction -I$(ENGINE_INC)/system/common -I$(ENGINE_INC)/system/statistics -I$(ENGINE_INC)/system/transaction_logger -I$(ENGINE_INC)/system/transaction_logger/asynchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/synchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/group_synchronous_redo_log -I$(ENGINE_INC)/system/checkpoint -I$(ENGINE_INC)/system/recovery -I$(ENGINE_INC)/utils +override CXXFLAGS += -I$(ENGINE_INC)/infra -I$(ENGINE_INC)/infra/config -I$(ENGINE_INC)/infra/containers -I$(ENGINE_INC)/infra/stats -I$(ENGINE_INC)/infra/synchronization -I$(ENGINE_INC)/concurrency_control -I$(ENGINE_INC)/storage/index -I$(ENGINE_INC)/storage/sentinel -I$(ENGINE_INC)/system/transaction -I$(ENGINE_INC)/system/common -I$(ENGINE_INC)/system/statistics -I$(ENGINE_INC)/system/transaction_logger -I$(ENGINE_INC)/system/transaction_logger/asynchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/synchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/group_synchronous_redo_log -I$(ENGINE_INC)/system/checkpoint -I$(ENGINE_INC)/system/recovery -I$(ENGINE_INC)/utils override CXXFLAGS += -faligned-new +ifeq ($(enable_cassert), yes) +override CXXFLAGS += -DDEBUG +endif + $(OBJS): | buildrepo install: install-data @@ -81,6 +85,7 @@ $(OBJ_DIR)/%.o: %.cpp show: @echo "CC_VERSION=${CC_VERSION}" + @echo "enable_cassert=${enable_cassert}" @echo "CC=${CC}" @echo @echo "DEBUG=${DEBUG}" diff --git a/src/gausskernel/storage/mot/fdw_adapter/gaussdb_config_loader.h b/src/gausskernel/storage/mot/fdw_adapter/gaussdb_config_loader.h index e1771cf78..c8b7785cd 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/gaussdb_config_loader.h +++ b/src/gausskernel/storage/mot/fdw_adapter/gaussdb_config_loader.h @@ -174,10 +174,11 @@ private: uint64_t maxReserveMemoryMb = globalMemoryMb + localMemoryMb + sessionLargeStoreMb; // if the total memory is less than the required minimum, then issue a warning, fix it and return - if (maxReserveMemoryMb < motCfg.MOT_MIN_MEMORY_USAGE_MB) { + if (maxReserveMemoryMb < MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB) { MOT_LOG_WARN("MOT memory limits are too low, adjusting values"); // we use current total as zero to force session large store zero value - result = ConfigureMemoryLimits(motCfg.MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); + result = + ConfigureMemoryLimits(MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); return result; } @@ -233,14 +234,16 @@ private: // this can happen only if system memory is less than 2GB, we still allow minor breach MOT_LOG_WARN("Using minimal memory limits in MOT Engine due to system total memory restrictions"); // we use current total as zero to force session large store zero value - result = ConfigureMemoryLimits(motCfg.MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); + result = ConfigureMemoryLimits( + MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); } else { newTotalMemoryMb = upperLimitMb - dynamicGapMb; - if (newTotalMemoryMb < motCfg.MOT_MIN_MEMORY_USAGE_MB) { + if (newTotalMemoryMb < MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB) { // in extreme cases we allow a minor breach of the dynamic gap MOT_LOG_TRACE("Using minimal memory limits in MOT Engine due to GaussDB memory usage restrictions"); // we use current total as zero to force session large store zero value - result = ConfigureMemoryLimits(motCfg.MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); + result = ConfigureMemoryLimits( + MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB, 0, globalMemoryMb, localMemoryMb); } else { MOT_LOG_TRACE("Adjusting memory limits in MOT Engine due to GaussDB memory usage restrictions"); result = ConfigureMemoryLimits(newTotalMemoryMb, maxReserveMemoryMb, globalMemoryMb, localMemoryMb); @@ -284,13 +287,13 @@ private: } // when current total memory is zero we use the minimum allowed, and also when minimum values are breached - if ((newGlobalMemoryMb < motCfg.MIN_MAX_MOT_GLOBAL_MEMORY_MB) || - (newLocalMemoryMb < motCfg.MIN_MAX_MOT_LOCAL_MEMORY_MB)) { + if ((newGlobalMemoryMb < MOT::MOTConfiguration::MIN_MAX_MOT_GLOBAL_MEMORY_MB) || + (newLocalMemoryMb < MOT::MOTConfiguration::MIN_MAX_MOT_LOCAL_MEMORY_MB)) { if (currentTotalMemoryMb > 0) { MOT_LOG_TRACE("Adjusted values breach minimum restrictions, falling back to minimum values"); } - newGlobalMemoryMb = motCfg.MIN_MAX_MOT_GLOBAL_MEMORY_MB; - newLocalMemoryMb = motCfg.MIN_MAX_MOT_LOCAL_MEMORY_MB; + newGlobalMemoryMb = MOT::MOTConfiguration::MIN_MAX_MOT_GLOBAL_MEMORY_MB; + newLocalMemoryMb = MOT::MOTConfiguration::MIN_MAX_MOT_LOCAL_MEMORY_MB; newSessionLargeStoreMemoryMb = 0; } @@ -303,15 +306,30 @@ private: // stream into MOT new definitions MOT::mot_string memCfg; - memCfg.format("%" PRIu64 " MB", newGlobalMemoryMb); - bool result = AddExtStringConfigItem("", "max_mot_global_memory", memCfg.c_str()); + bool result = memCfg.format("%" PRIu64 " MB", newGlobalMemoryMb); + if (!result) { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Load Configuration", "Failed to format global memory configuration string"); + } else { + result = AddExtStringConfigItem("", "max_mot_global_memory", memCfg.c_str()); + } if (result) { - memCfg.format("%" PRIu64 " MB", newLocalMemoryMb); - result = AddExtStringConfigItem("", "max_mot_local_memory", memCfg.c_str()); + result = memCfg.format("%" PRIu64 " MB", newLocalMemoryMb); + if (!result) { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Load Configuration", "Failed to format local memory configuration string"); + } else { + result = AddExtStringConfigItem("", "max_mot_local_memory", memCfg.c_str()); + } } if (result && (motCfg.m_sessionLargeBufferStoreSizeMB != newSessionLargeStoreMemoryMb)) { - memCfg.format("%" PRIu64 " MB", newSessionLargeStoreMemoryMb); - result = AddExtStringConfigItem("", "session_large_buffer_store_size", memCfg.c_str()); + result = memCfg.format("%" PRIu64 " MB", newSessionLargeStoreMemoryMb); + if (!result) { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Load Configuration", "Failed to format session memory configuration string"); + } else { + result = AddExtStringConfigItem("", "session_large_buffer_store_size", memCfg.c_str()); + } } // Reset pre-allocation to zero, as it may be invalid now diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp index 4c8320839..8ec54619e 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp @@ -1,6 +1,5 @@ /* * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 2010-2012, PostgreSQL Global Development Group * * openGauss is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. @@ -72,13 +71,13 @@ #include "storage/ipc.h" #include "mot_internal.h" +#include "mot_fdw_helpers.h" #include "storage/mot/jit_exec.h" #include "mot_engine.h" #include "table.h" #include "txn.h" #include "checkpoint_manager.h" #include -#include "recovery_manager.h" #include "redo_log_handler_type.h" #include "ext_config_loader.h" #include "utilities.h" @@ -95,13 +94,10 @@ struct MOTFdwOption { }; /* - * Valid options for file_fdw. + * Valid options for mot_fdw. * These options are based on the options for COPY FROM command. * But note that force_not_null is handled as a boolean option attached to * each column, not as a table option. - * - * Note: If you are adding new option for user mapping, you need to modify - * fileGetOptions(), which currently doesn't bother to look at user mappings. */ static const struct MOTFdwOption valid_options[] = { @@ -163,29 +159,16 @@ static void MOTNotifyForeignConfigChange(); static void MOTCheckpointCallback(CheckpointEvent checkpointEvent, uint64_t lsn, void* arg); -/* - * Helper functions - */ -static bool IsValidOption(const char* option, Oid context); static void MOTValidateTableDef(Node* obj); static int MOTIsForeignRelationUpdatable(Relation rel); -static void InitMOTHandler(); -MOTFdwStateSt* InitializeFdwState(void* fdwState, List** fdwExpr, uint64_t exTableId); -void* SerializeFdwState(MOTFdwStateSt* fdwState); -void ReleaseFdwState(MOTFdwStateSt* fdwState); -void CleanCursors(MOTFdwStateSt* state); -void CleanQueryStatesOnError(MOT::TxnManager* txn); - -/* Query */ -bool IsMOTExpr( - RelOptInfo* baserel, MOTFdwStateSt* state, MatchIndexArr* marr, Expr* expr, Expr** result, bool setLocal); -inline bool IsNotEqualOper(OpExpr* op); static int MOTGetFdwType() { return MOT_ORC; } +static void InitMOTHandler(); + void MOTRecover() { if (!MOTAdaptor::m_initialized) { @@ -193,7 +176,7 @@ void MOTRecover() return; } - EnsureSafeThreadAccess(); + (void)EnsureSafeThreadAccess(); if (!MOT::MOTEngine::GetInstance()->StartRecovery()) { // we treat errors fatally. ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("MOT checkpoint recovery failed."))); @@ -213,9 +196,8 @@ void MOTRecoveryDone() return; } - EnsureSafeThreadAccess(); + (void)EnsureSafeThreadAccess(); if (!MOT::MOTEngine::GetInstance()->EndRecovery()) { - ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("MOT recovery failed."))); } } @@ -229,7 +211,7 @@ void MOTBeginRedoRecovery() return; } - EnsureSafeThreadAccess(); + (void)EnsureSafeThreadAccess(); if (!MOT::MOTEngine::GetInstance()->CreateRecoverySessionContext()) { // we treat errors fatally. ereport(FATAL, (errmsg("MOTBeginRedoRecovery: failed to create session context."))); @@ -248,28 +230,24 @@ void MOTEndRedoRecovery() return; } - EnsureSafeThreadAccess(); + (void)EnsureSafeThreadAccess(); MOT::MOTEngine::GetInstance()->DestroyRecoverySessionContext(); knl_thread_mot_init(); // reset all thread locals } /* - * This function should be called upon startup in order to enable xlog replay into - * mot. We call it in mot_fdw_handler just in case + * Initializes the engine. */ void InitMOT() { - if (MOTAdaptor::m_initialized) { - // MOT is already initialized, probably it's primary switch-over to standby. - return; - } - InitMOTHandler(); - JitExec::JitInitialize(); + + // if JIT initialization failed we continue anyway without JIT + (void)JitExec::JitInitialize(); } -/** - * Shutdown the engine +/* + * Shutdown the engine. */ void TermMOT() { @@ -318,7 +296,6 @@ Datum mot_fdw_handler(PG_FUNCTION_ARGS) fdwroutine->NotifyForeignConfigChange = MOTNotifyForeignConfigChange; if (!u_sess->mot_cxt.callbacks_set) { - GetCurrentTransactionIdIfAny(); RegisterXactCallback(MOTXactCallback, NULL); RegisterSubXactCallback(MOTSubxactCallback, NULL); u_sess->mot_cxt.callbacks_set = true; @@ -326,7 +303,8 @@ Datum mot_fdw_handler(PG_FUNCTION_ARGS) PG_TRY(); { - MOTAdaptor::InitTxnManager(__FUNCTION__); + // we don't care if null is returned when the engine is not created yet + (void)MOTAdaptor::InitTxnManager(__FUNCTION__); } PG_CATCH(); { @@ -336,9 +314,25 @@ Datum mot_fdw_handler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(fdwroutine); } +/* + * Check if the provided option is one of the valid options. + * context is the Oid of the catalog holding the object the option is for. + */ +static bool IsValidOption(const char* option, Oid context) +{ + const struct MOTFdwOption* opt; + + for (opt = valid_options; opt->m_optname; opt++) { + if (context == opt->m_optcontext && strcmp(opt->m_optname, option) == 0) { + return true; + } + } + return false; +} + /* * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, - * USER MAPPING or FOREIGN TABLE that uses file_fdw. + * USER MAPPING or FOREIGN TABLE that uses mot_fdw. * * Raise an ERROR if the option or its value is considered invalid. */ @@ -387,21 +381,6 @@ Datum mot_fdw_validator(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -/* - * Check if the provided option is one of the valid options. - * context is the Oid of the catalog holding the object the option is for. - */ -static bool IsValidOption(const char* option, Oid context) -{ - const struct MOTFdwOption* opt; - - for (opt = valid_options; opt->m_optname; opt++) { - if (context == opt->m_optcontext && strcmp(opt->m_optname, option) == 0) - return true; - } - return false; -} - /* * Check if there is any memory management module error. * If there is any, abort the whole transaction. @@ -417,13 +396,18 @@ static void MemoryEreportError() } /* - * + * Estimates the number of rows and width of the result of the scan on the MOT table. */ static void MOTGetForeignRelSize(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { MOTFdwStateSt* planstate = (MOTFdwStateSt*)palloc0(sizeof(MOTFdwStateSt)); ForeignTable* ftable = GetForeignTable(foreigntableid); MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + + if (IsTxnInAbortState(currTxn)) { + raiseAbortTxnError(); + } + Bitmapset* attrs = nullptr; ListCell* lc = nullptr; bool needWholeRow = false; @@ -475,8 +459,6 @@ static void MOTGetForeignRelSize(PlannerInfo* root, RelOptInfo* baserel, Oid for baserel->rows = planstate->m_table->GetRowCount(); baserel->tuples = planstate->m_table->GetRowCount(); - if (baserel->rows == 0) - baserel->rows = baserel->tuples = 100000; planstate->m_startupCost = 0.1; planstate->m_totalCost = baserel->rows * planstate->m_startupCost; @@ -487,10 +469,11 @@ static bool IsOrderingApplicable(PathKey* pathKey, RelOptInfo* rel, MOT::Index* { bool res = false; - if (ord->m_order == SORTDIR_ENUM::SORTDIR_NONE) + if (ord->m_order == SortDir::SORTDIR_NONE) { ord->m_order = SORT_STRATEGY(pathKey->pk_strategy); - else if (ord->m_order != SORT_STRATEGY(pathKey->pk_strategy)) + } else if (ord->m_order != SORT_STRATEGY(pathKey->pk_strategy)) { return res; + } do { const int16_t* cols = ix->GetColumnKeyFields(); @@ -501,8 +484,16 @@ static bool IsOrderingApplicable(PathKey* pathKey, RelOptInfo* rel, MOT::Index* EquivalenceMember* em = (EquivalenceMember*)lfirst(lcEm); if (bms_equal(em->em_relids, rel->relids)) { + Var* v = nullptr; if (IsA(em->em_expr, Var)) { - Var* v = (Var*)(em->em_expr); + v = (Var*)(em->em_expr); + } else if (IsA(em->em_expr, RelabelType)) { + RelabelType* rv = (RelabelType*)(em->em_expr); + if (rv->relabelformat == COERCE_DONTCARE && IsA(rv->arg, Var)) { + v = (Var*)(rv->arg); + } + } + if (v != nullptr) { int i = 0; for (; i < numKeyCols; i++) { if (cols[i] == v->varattno) { @@ -532,7 +523,7 @@ static bool IsOrderingApplicable(PathKey* pathKey, RelOptInfo* rel, MOT::Index* } /* - * + * Creates possible scan paths for a scan on the MOT table. */ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { @@ -546,12 +537,18 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei Path* fpReg = nullptr; Path* fpIx = nullptr; bool hasRegularPath = false; + MOT::Index* pix = planstate->m_table->GetPrimaryIndex(); + double ntuples = baserel->tuples; - planstate->m_order = SORTDIR_ENUM::SORTDIR_ASC; + ntuples = ntuples * clauselist_selectivity(root, baserel->baserestrictinfo, 0, JOIN_INNER, nullptr); + baserel->rows = clamp_row_est(ntuples); + planstate->m_order = SortDir::SORTDIR_ASC; // first create regular path based on relation restrictions foreach (lc, baserel->baserestrictinfo) { RestrictInfo* ri = (RestrictInfo*)lfirst(lc); - + if (ri->pseudoconstant) { + continue; + } if (!IsMOTExpr(baserel, planstate, &marr, ri->clause, nullptr, true)) { planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); } @@ -562,10 +559,6 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei if (best != nullptr) { OrderSt ord; ord.init(); - double ntuples = best->m_cost; - ntuples = ntuples * clauselist_selectivity(root, bestClause, 0, JOIN_INNER, nullptr); - ntuples = clamp_row_est(ntuples); - baserel->rows = baserel->tuples = ntuples; planstate->m_startupCost = 0.001; planstate->m_totalCost = best->m_cost; planstate->m_bestIx = best; @@ -581,15 +574,13 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei if (!best->CanApplyOrdering(ord.m_cols)) { list_free(usablePathkeys); usablePathkeys = nullptr; - } else if (!best->AdjustForOrdering((ord.m_order == SORTDIR_ENUM::SORTDIR_DESC))) { + } else if (!best->AdjustForOrdering((ord.m_order == SortDir::SORTDIR_DESC))) { list_free(usablePathkeys); usablePathkeys = nullptr; } - best = nullptr; } else if (list_length(root->query_pathkeys) > 0) { OrderSt ord; ord.init(); - MOT::Index* ix = planstate->m_table->GetPrimaryIndex(); List* keys; if (root->query_level == 1) @@ -600,7 +591,7 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei foreach (lc, keys) { PathKey* pathkey = (PathKey*)lfirst(lc); - if (!pathkey->pk_eclass->ec_has_volatile && IsOrderingApplicable(pathkey, baserel, ix, &ord)) { + if (!pathkey->pk_eclass->ec_has_volatile && IsOrderingApplicable(pathkey, baserel, pix, &ord)) { usablePathkeys = lappend(usablePathkeys, pathkey); } } @@ -613,18 +604,26 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei usablePathkeys = nullptr; } } else - planstate->m_order = SORTDIR_ENUM::SORTDIR_ASC; + planstate->m_order = SortDir::SORTDIR_ASC; } + ereport(DEBUG1, + (errmodule(MOD_MOT), + errmsg("FP regular for %s [rows: %f, scost: %f, tcost: %f] ix: %s", + planstate->m_table->GetTableName().c_str(), + baserel->rows, + planstate->m_startupCost, + planstate->m_totalCost, + (best ? best->m_ix->GetName().c_str() : "SCAN")))); fpReg = (Path*)create_foreignscan_path(root, baserel, planstate->m_startupCost, planstate->m_totalCost, usablePathkeys, - nullptr, /* no outer rel either */ - nullptr, // private data will be assigned later + nullptr, /* no outer rel either */ + (best ? lappend(nullptr, (void*)best) : nullptr), 0); - + best = nullptr; foreach (lc, baserel->pathlist) { Path* path = (Path*)lfirst(lc); if (IsA(path, IndexPath) && path->param_info == nullptr) { @@ -636,68 +635,139 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei add_path(root, baserel, fpReg); set_cheapest(baserel); - if (!IS_PGXC_COORDINATOR && list_length(baserel->cheapest_parameterized_paths) > 0) { - foreach (lc, baserel->cheapest_parameterized_paths) { + int arrLen = list_length(baserel->pathlist); + Path* fpIxArr[arrLen + 1]; + int fpIxCount = 0; + if (!IS_PGXC_COORDINATOR && arrLen > 0) { + double prevCost = 0.0; + foreach (lc, baserel->pathlist) { bestPath = (Path*)lfirst(lc); - if (IsA(bestPath, IndexPath) && bestPath->param_info) { + if (IsA(bestPath, IndexPath)) { + ListCell* lcp = nullptr; + OrderSt ord; IndexPath* ip = (IndexPath*)bestPath; + MOT::Index* six = nullptr; + + ord.init(); bestClause = ip->indexclauses; - break; - } - } - usablePathkeys = nullptr; - } + usablePathkeys = nullptr; + marr.Clear(); + marr.m_ixOid = ip->indexinfo->indexoid; + best = nullptr; + ereport(DEBUG1, + (errmodule(MOD_MOT), + errmsg("Index path %s (cnum %d) for oid %u [rows: %f, scost: %f, tcost: %f]", + (ip->path.param_info ? "with params" : "without params"), + list_length(bestClause), + ip->indexinfo->indexoid, + ip->path.rows, + ip->path.startup_cost, + ip->path.total_cost))); - if (bestClause != nullptr) { - marr.Clear(); + if (bestClause != nullptr) { + foreach (lcp, bestClause) { + RestrictInfo* ri = (RestrictInfo*)lfirst(lcp); + if (ri->pseudoconstant) { + continue; + } + // In case we use index params DO NOT add it to envelope filter. + (void)IsMOTExpr(baserel, planstate, &marr, ri->clause, nullptr, false); + } - foreach (lc, bestClause) { - RestrictInfo* ri = (RestrictInfo*)lfirst(lc); - - // In case we use index params DO NOT add it to envelope filter. - (void)IsMOTExpr(baserel, planstate, &marr, ri->clause, nullptr, false); - } - - best = MOTAdaptor::GetBestMatchIndex(planstate, &marr, list_length(bestClause), false); - if (best != nullptr) { - OrderSt ord; - ord.init(); - double ntuples = best->m_cost; - ntuples = ntuples * clauselist_selectivity(root, bestClause, 0, JOIN_INNER, nullptr); - ntuples = clamp_row_est(ntuples); - baserel->rows = baserel->tuples = ntuples; - planstate->m_paramBestIx = best; - planstate->m_startupCost = 0.001; - planstate->m_totalCost = best->m_cost; - - foreach (lc, root->query_pathkeys) { - PathKey* pathkey = (PathKey*)lfirst(lc); - if (!pathkey->pk_eclass->ec_has_volatile && IsOrderingApplicable(pathkey, baserel, best->m_ix, &ord)) { - usablePathkeys = lappend(usablePathkeys, pathkey); + best = MOTAdaptor::GetBestMatchIndex(planstate, &marr, list_length(bestClause), false); } + if (best != nullptr) { + six = best->m_ix; + if (best->m_ix->GetExtId() == ip->indexinfo->indexoid) { + baserel->rows = clamp_row_est(ip->indexselectivity * ip->path.parent->tuples); + planstate->m_startupCost = 0.0; + planstate->m_totalCost = ip->indextotalcost; + } else { + ntuples = baserel->tuples; + ntuples = ntuples * clauselist_selectivity(root, bestClause, 0, JOIN_INNER, nullptr); + baserel->rows = clamp_row_est(ntuples); + planstate->m_startupCost = 0.001; + planstate->m_totalCost = ip->indextotalcost; + } + if (prevCost == planstate->m_totalCost) { + planstate->m_totalCost += 0.01; + } + prevCost = planstate->m_totalCost; + foreach (lcp, ip->path.pathkeys) { + PathKey* pathkey = (PathKey*)lfirst(lcp); + if (!pathkey->pk_eclass->ec_has_volatile && + IsOrderingApplicable(pathkey, baserel, best->m_ix, &ord)) { + usablePathkeys = lappend(usablePathkeys, pathkey); + } + } + + if (!best->CanApplyOrdering(ord.m_cols)) { + list_free(usablePathkeys); + usablePathkeys = nullptr; + } else if (!best->AdjustForOrdering((ord.m_order == SortDir::SORTDIR_DESC))) { + list_free(usablePathkeys); + usablePathkeys = nullptr; + } + + if (ip->path.param_info == nullptr) { + hasRegularPath = false; + } + } else { + int pos = -1; + + best = (MatchIndex*)palloc0(sizeof(MatchIndex)); + six = planstate->m_table->GetIndexByExtIdWithPos(ip->indexinfo->indexoid, pos); + MOT_ASSERT(six != nullptr); + best->Init(); + best->m_ix = six; + best->m_fullScan = true; + best->m_ixPosition = pos; + foreach (lcp, ip->path.pathkeys) { + PathKey* pathkey = (PathKey*)lfirst(lcp); + if (!pathkey->pk_eclass->ec_has_volatile && IsOrderingApplicable(pathkey, baserel, six, &ord)) { + usablePathkeys = lappend(usablePathkeys, pathkey); + } + } + + if (list_length(usablePathkeys) > 0) { + if (ord.m_cols[0] != 0) + best->m_order = ord.m_order; + else { + list_free(usablePathkeys); + usablePathkeys = nullptr; + } + } + + bestClause = baserel->baserestrictinfo; + ntuples = baserel->tuples; + ntuples = ntuples * clauselist_selectivity(root, bestClause, 0, JOIN_INNER, nullptr); + baserel->rows = clamp_row_est(ntuples); + planstate->m_startupCost = 0.0; + planstate->m_totalCost = ip->path.total_cost; + } + fpIx = (Path*)create_foreignscan_path(root, + baserel, + planstate->m_startupCost, + planstate->m_totalCost, + usablePathkeys, + nullptr, /* no outer rel either */ + lappend(nullptr, (void*)best), + ip->path.dop); + + fpIx->param_info = bestPath->param_info; + ereport(DEBUG1, + (errmodule(MOD_MOT), + errmsg("FP index for %s [rows: %f, scost: %f, tcost: %f] ix: %s", + planstate->m_table->GetTableName().c_str(), + baserel->rows, + planstate->m_startupCost, + planstate->m_totalCost, + six->GetName().c_str()))); + fpIxArr[fpIxCount] = fpIx; + fpIxCount++; } - - if (!best->CanApplyOrdering(ord.m_cols)) { - list_free(usablePathkeys); - usablePathkeys = nullptr; - } else if (!best->AdjustForOrdering((ord.m_order == SORTDIR_ENUM::SORTDIR_DESC))) { - list_free(usablePathkeys); - usablePathkeys = nullptr; - } - - fpIx = (Path*)create_foreignscan_path(root, - baserel, - planstate->m_startupCost, - planstate->m_totalCost, - usablePathkeys, - nullptr, /* no outer rel either */ - nullptr, // private data will be assigned later - 0); - - fpIx->param_info = bestPath->param_info; } } - List* newPath = nullptr; List* origPath = baserel->pathlist; // disable index path @@ -713,13 +783,14 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei baserel->pathlist = newPath; if (hasRegularPath) add_path(root, baserel, fpReg); - if (fpIx != nullptr) - add_path(root, baserel, fpIx); + for (int i = 0; i < fpIxCount; i++) { + add_path(root, baserel, fpIxArr[i]); + } set_cheapest(baserel); } /* - * + * Creates ForeignScanPlan for the selected best path. */ static ForeignScan* MOTGetForeignPlan( PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses) @@ -730,6 +801,17 @@ static ForeignScan* MOTGetForeignPlan( List* tmpLocal = nullptr; List* remote = nullptr; + MatchIndex* mix = + (MatchIndex*)(list_length(best_path->fdw_private) > 0 ? linitial(best_path->fdw_private) : nullptr); + planstate->m_paramBestIx = best_path->path.param_info ? mix : nullptr; + ereport(DEBUG1, + (errmodule(MOD_MOT), + errmsg("Plan for %s [rows: %f, scost: %f, tcost: %f] ix: %s", + planstate->m_table->GetTableName().c_str(), + best_path->path.rows, + best_path->path.startup_cost, + best_path->path.total_cost, + (mix ? mix->m_ix->GetName().c_str() : "SCAN")))); if (best_path->path.param_info && planstate->m_paramBestIx) { if (planstate->m_bestIx != nullptr) { planstate->m_bestIx->Clean(planstate); @@ -737,9 +819,18 @@ static ForeignScan* MOTGetForeignPlan( } planstate->m_bestIx = planstate->m_paramBestIx; planstate->m_paramBestIx = nullptr; + } else if (planstate->m_bestIx != mix) { + if (planstate->m_bestIx != nullptr) { + planstate->m_bestIx->Clean(planstate); + pfree(planstate->m_bestIx); + } + planstate->m_bestIx = mix; } if (planstate->m_bestIx != nullptr) { + if (planstate->m_bestIx->m_fullScan) { + planstate->m_order = planstate->m_bestIx->m_order; + } planstate->m_numExpr = list_length(planstate->m_bestIx->m_remoteConds); remote = list_concat(planstate->m_bestIx->m_remoteConds, planstate->m_bestIx->m_remoteCondsOrig); @@ -756,31 +847,21 @@ static ForeignScan* MOTGetForeignPlan( foreach (lc, scan_clauses) { RestrictInfo* ri = (RestrictInfo*)lfirst(lc); - - // add OR conditions which where not handled by previous functions - if (ri->orclause != nullptr) - planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); - else if (IsA(ri->clause, BoolExpr)) { - BoolExpr* e = (BoolExpr*)ri->clause; - - if (e->boolop == NOT_EXPR) - planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); - } else if (IsA(ri->clause, OpExpr)) { + if (ri->pseudoconstant) { + continue; + } + if (IsA(ri->clause, OpExpr)) { OpExpr* e = (OpExpr*)ri->clause; if (IsNotEqualOper(e)) planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); else if (!list_member(remote, e)) planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); + } else { + planstate->m_localConds = lappend(planstate->m_localConds, ri->clause); } } - foreach (lc, tmpLocal) { - Expr* e = (Expr*)lfirst(lc); - if (!list_member(planstate->m_localConds, e)) - planstate->m_localConds = lappend(planstate->m_localConds, e); - } - if (tmpLocal != nullptr) list_free(tmpLocal); @@ -800,8 +881,7 @@ static ForeignScan* MOTGetForeignPlan( } /* - * - * Produce extra output for EXPLAIN + * Produce extra output for EXPLAIN of a scan on the MOT table. */ static void MOTExplainForeignScan(ForeignScanState* node, ExplainState* es) { @@ -809,9 +889,9 @@ static void MOTExplainForeignScan(ForeignScanState* node, ExplainState* es) bool isLocal = false; ForeignScan* fscan = (ForeignScan*)node->ss.ps.plan; - if (node->fdw_state != nullptr) + if (node->fdw_state != nullptr) { festate = (MOTFdwStateSt*)node->fdw_state; - else { + } else { festate = InitializeFdwState(fscan->fdw_private, &fscan->fdw_exprs, RelationGetRelid(node->ss.ss_currentRelation)); isLocal = true; @@ -856,7 +936,7 @@ static void MOTExplainForeignScan(ForeignScanState* node, ExplainState* es) } /* - * + * Initiates a scan on the MOT table. */ static void MOTBeginForeignScan(ForeignScanState* node, int eflags) { @@ -869,16 +949,17 @@ static void MOTBeginForeignScan(ForeignScanState* node, int eflags) MOTAdaptor::GetCmdOper(festate); festate->m_txnId = GetCurrentTransactionIdIfAny(); festate->m_currTxn = GetSafeTxn(__FUNCTION__); - festate->m_currTxn->IncStmtCount(); - festate->m_currTxn->m_queryState[(uint64_t)festate] = (uint64_t)festate; festate->m_table = festate->m_currTxn->GetTableByExternalId(RelationGetRelid(node->ss.ss_currentRelation)); node->fdw_state = festate; if (node->ss.ps.state->es_result_relation_info && RelationGetRelid(node->ss.ps.state->es_result_relation_info->ri_RelationDesc) == RelationGetRelid(node->ss.ss_currentRelation)) node->ss.ps.state->es_result_relation_info->ri_FdwState = festate; - festate->m_currTxn->SetTxnIsoLevel(u_sess->utils_cxt.XactIsoLevel); + festate->m_currTxn->SetIsolationLevel(u_sess->utils_cxt.XactIsoLevel); + if (IsTxnInAbortState(festate->m_currTxn)) { + raiseAbortTxnError(); + } foreach (t, node->ss.ps.plan->targetlist) { TargetEntry* tle = (TargetEntry*)lfirst(t); Var* v = (Var*)tle->expr; @@ -894,13 +975,14 @@ static void MOTBeginForeignModify( { MOTFdwStateSt* festate = nullptr; + (void)GetCurrentTransactionId(); + if (fdwPrivate != nullptr && resultRelInfo->ri_FdwState == nullptr) { isMemoryLimitReached(); festate = InitializeFdwState(fdwPrivate, nullptr, RelationGetRelid(resultRelInfo->ri_RelationDesc)); festate->m_allocInScan = false; festate->m_txnId = GetCurrentTransactionIdIfAny(); festate->m_currTxn = GetSafeTxn(__FUNCTION__); - festate->m_currTxn->m_queryState[(uint64_t)festate] = (uint64_t)festate; festate->m_table = festate->m_currTxn->GetTableByExternalId(RelationGetRelid(resultRelInfo->ri_RelationDesc)); resultRelInfo->ri_FdwState = festate; } else { @@ -920,7 +1002,7 @@ static void MOTBeginForeignModify( int len = BITMAP_GETLEN(festate->m_numAttrs); if (fdwPrivate != nullptr) { ListCell* cell = list_head(fdwPrivate); - BitmapDeSerialize(festate->m_attrsModified, len, &cell); + BitmapDeSerialize(festate->m_attrsModified, len, festate->m_hasIndexedColUpdate, &cell); for (int i = 0; i < festate->m_numAttrs; i++) { if (BITMAP_GET(festate->m_attrsModified, i)) { @@ -934,12 +1016,15 @@ static void MOTBeginForeignModify( securec_check(erc, "\0", "\0"); } } + if (IsTxnInAbortState(festate->m_currTxn)) { + raiseAbortTxnError(); + } // Update FDW operation festate->m_ctidNum = ExecFindJunkAttributeInTlist(mtstate->mt_plans[subplanIndex]->plan->targetlist, MOT_REC_TID_NAME); festate->m_cmdOper = mtstate->operation; MOTAdaptor::GetCmdOper(festate); - festate->m_currTxn->SetTxnIsoLevel(u_sess->utils_cxt.XactIsoLevel); + festate->m_currTxn->SetIsolationLevel(u_sess->utils_cxt.XactIsoLevel); } static TupleTableSlot* IterateForeignScanStopAtFirst( @@ -950,15 +1035,15 @@ static TupleTableSlot* IterateForeignScanStopAtFirst( festate->m_execExprs = (List*)ExecInitExpr((Expr*)fscan->fdw_exprs, (PlanState*)node); festate->m_econtext = node->ss.ps.ps_ExprContext; MOTAdaptor::CreateKeyBuffer(node->ss.ss_currentRelation, festate, 0); - MOT::Sentinel* Sentinel = + MOT::Sentinel* sentinel = festate->m_bestIx->m_ix->IndexReadSentinel(&festate->m_stateKey[0], festate->m_currTxn->GetThdId()); - MOT::Row* currRow = festate->m_currTxn->RowLookup(festate->m_internalCmdOper, Sentinel, rc); + MOT::Row* currRow = festate->m_currTxn->RowLookup(festate->m_internalCmdOper, sentinel, rc); if (currRow != NULL) { MOTAdaptor::UnpackRow(slot, festate->m_table, festate->m_attrsUsed, const_cast(currRow->GetData())); node->ss.is_scan_end = true; fscan->scan.scan_qual_optimized = true; - ExecStoreVirtualTuple(slot); + (void)ExecStoreVirtualTuple(slot); if (festate->m_ctidNum > 0) { HeapTuple resultTup = ExecFetchSlotTuple(slot); MOTRecConvertSt cv; @@ -972,6 +1057,12 @@ static TupleTableSlot* IterateForeignScanStopAtFirst( return slot; } if (rc != MOT::RC_OK) { + if (rc == MOT::RC_ABORT) { + abortParentTransactionParamsNoDetail(ERRCODE_T_R_SERIALIZATION_FAILURE, + "Commit: could not serialize access due to concurrent update(%d)", + 0); + return nullptr; + } if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "MOTIterateForeignScan", "Failed to lookup row"); MOT_LOG_ERROR_STACK("Failed to lookup row"); @@ -987,7 +1078,7 @@ static TupleTableSlot* IterateForeignScanStopAtFirst( } /* - * + * Iterates to fetch the next row or null to indicate the end of the scan. */ static TupleTableSlot* MOTIterateForeignScan(ForeignScanState* node) { @@ -1037,10 +1128,16 @@ static TupleTableSlot* MOTIterateForeignScan(ForeignScanState* node) } do { - MOT::Sentinel* Sentinel = festate->m_cursor[0]->GetPrimarySentinel(); - currRow = festate->m_currTxn->RowLookup(festate->m_internalCmdOper, Sentinel, rc); + MOT::Sentinel* sentinel = festate->m_cursor[0]->GetPrimarySentinel(); + currRow = festate->m_currTxn->RowLookup(festate->m_internalCmdOper, sentinel, rc); if (currRow == NULL) { if (rc != MOT::RC_OK) { + if (rc == MOT::RC_ABORT) { + abortParentTransactionParamsNoDetail(ERRCODE_T_R_SERIALIZATION_FAILURE, + "Commit: could not serialize access due to concurrent update(%d)", + 0); + return NULL; + } if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "MOTIterateForeignScan", "Failed to lookup row"); MOT_LOG_ERROR_STACK("Failed to lookup row"); @@ -1072,7 +1169,7 @@ static TupleTableSlot* MOTIterateForeignScan(ForeignScanState* node) } while (festate->m_cursor[0]->IsValid()); if (found) { - ExecStoreVirtualTuple(slot); + (void)ExecStoreVirtualTuple(slot); if (festate->m_ctidNum > 0) { HeapTuple resultTup = ExecFetchSlotTuple(slot); @@ -1096,7 +1193,10 @@ static TupleTableSlot* MOTIterateForeignScan(ForeignScanState* node) static void MOTReScanForeignScan(ForeignScanState* node) { MOTFdwStateSt* festate = (MOTFdwStateSt*)node->fdw_state; - + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + if (IsTxnInAbortState(txn)) { + raiseAbortTxnError(); + } bool stopAtFirst = (festate->m_bestIx && festate->m_bestIx->m_ixOpers[0] == KEY_OPER::READ_KEY_EXACT && festate->m_bestIx->m_ix->GetUnique() == true); @@ -1181,8 +1281,8 @@ static int MOTAcquireSampleRowsFunc(Relation relation, int elevel, HeapTuple* ro while (cursor->IsValid()) { row = NULL; - MOT::Sentinel* Sentinel = cursor->GetPrimarySentinel(); - row = currTxn->RowLookup(MOT::AccessType::RD, Sentinel, rc); + MOT::Sentinel* sentinel = cursor->GetPrimarySentinel(); + row = currTxn->RowLookup(MOT::AccessType::RD, sentinel, rc); cursor->Next(); if (row == NULL) { @@ -1205,8 +1305,9 @@ static int MOTAcquireSampleRowsFunc(Relation relation, int elevel, HeapTuple* ro * of the relation. Same algorithm as in acquire_sample_rows in * analyze.c; see Jeff Vitter's paper. */ - if (rowstoskip < 0) + if (rowstoskip < 0) { rowstoskip = anl_get_next_S(samplerows, targrows, &rstate); + } if (rowstoskip <= 0) { /* Choose a random reservoir element to replace. */ @@ -1228,7 +1329,7 @@ static int MOTAcquireSampleRowsFunc(Relation relation, int elevel, HeapTuple* ro */ (void)ExecClearTuple(slot); MOTAdaptor::UnpackRow(slot, table, attrsUsed, const_cast(row->GetData())); - ExecStoreVirtualTuple(slot); + (void)ExecStoreVirtualTuple(slot); rows[pos] = ExecCopySlotTuple(slot); } } @@ -1273,7 +1374,57 @@ static bool MOTAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* fun return true; } -List* MOTPlanForeignModify(PlannerInfo* root, ModifyTable* plan, ::Index resultRelation, int subplanIndex) +static void PrepareAttributeList(ModifyTable* plan, RangeTblEntry* rte, Relation rel, MOTFdwStateSt* fdwState, + MOT::Table* table, uint8_t* ptrAttrsModify, MOT::UpdateIndexColumnType& ixUpd) +{ + TupleDesc desc = RelationGetDescr(rel); + switch (plan->operation) { + case CMD_INSERT: { + for (int i = 0; i < desc->natts; i++) { + if (!desc->attrs[i]->attisdropped) { + BITMAP_SET(ptrAttrsModify, (desc->attrs[i]->attnum - 1)); + } + } + break; + } + case CMD_UPDATE: { + for (int i = 0; i < desc->natts; i++) { + if (bms_is_member(desc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, rte->updatedCols)) { + if (MOTAdaptor::IsColumnIndexed(desc->attrs[i]->attnum, table)) { + if (table->GetPrimaryIndex()->IsFieldPresent(desc->attrs[i]->attnum)) { + ixUpd = MOT::UpdateIndexColumnType::UPDATE_COLUMN_PRIMARY; + ereport(ERROR, + (errcode(ERRCODE_FDW_UPDATE_INDEXED_FIELD_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Update of primary key column is not supported for memory table"))); + } else { + ixUpd = MOT::UpdateIndexColumnType::UPDATE_COLUMN_SECONDARY; + } + } + BITMAP_SET(ptrAttrsModify, (desc->attrs[i]->attnum - 1)); + } + } + if (fdwState != nullptr) { + fdwState->m_hasIndexedColUpdate = ixUpd; + } + break; + } + case CMD_DELETE: { + if (list_length(plan->returningLists) > 0) { + for (int i = 0; i < desc->natts; i++) { + if (!desc->attrs[i]->attisdropped) { + BITMAP_SET(ptrAttrsModify, (desc->attrs[i]->attnum - 1)); + } + } + } + break; + } + default: + break; + } +} + +static List* MOTPlanForeignModify(PlannerInfo* root, ModifyTable* plan, ::Index resultRelation, int subplanIndex) { switch (plan->operation) { case CMD_INSERT: @@ -1290,7 +1441,16 @@ List* MOTPlanForeignModify(PlannerInfo* root, ModifyTable* plan, ::Index resultR TupleDesc desc = RelationGetDescr(rel); uint8_t attrsModify[BITMAP_GETLEN(desc->natts)]; uint8_t* ptrAttrsModify = attrsModify; + MOT::UpdateIndexColumnType ixUpd = MOT::UpdateIndexColumnType::UPDATE_COLUMN_NONE; MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + + if (IsTxnInAbortState(currTxn)) { + raiseAbortTxnError(); + } + + errno_t erc = memset_s(attrsModify, BITMAP_GETLEN(desc->natts), 0, BITMAP_GETLEN(desc->natts)); + securec_check(erc, "\0", "\0"); + MOT::Table* table = currTxn->GetTableByExternalId(RelationGetRelid(rel)); if ((int)resultRelation < root->simple_rel_array_size && root->simple_rel_array[resultRelation] != nullptr) { @@ -1313,46 +1473,14 @@ List* MOTPlanForeignModify(PlannerInfo* root, ModifyTable* plan, ::Index resultR int len = BITMAP_GETLEN(fdwState->m_numAttrs); fdwState->m_attrsUsed = (uint8_t*)palloc0(len); fdwState->m_attrsModified = (uint8_t*)palloc0(len); + ptrAttrsModify = fdwState->m_attrsUsed; } - switch (plan->operation) { - case CMD_INSERT: { - for (int i = 0; i < desc->natts; i++) { - if (!desc->attrs[i]->attisdropped) { - BITMAP_SET(fdwState->m_attrsUsed, (desc->attrs[i]->attnum - 1)); - } - } - break; - } - case CMD_UPDATE: { - errno_t erc = memset_s(attrsModify, BITMAP_GETLEN(desc->natts), 0, BITMAP_GETLEN(desc->natts)); - securec_check(erc, "\0", "\0"); - for (int i = 0; i < desc->natts; i++) { - if (bms_is_member(desc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, rte->updatedCols)) { - BITMAP_SET(ptrAttrsModify, (desc->attrs[i]->attnum - 1)); - } - } - break; - } - case CMD_DELETE: { - if (list_length(plan->returningLists) > 0) { - errno_t erc = memset_s(attrsModify, BITMAP_GETLEN(desc->natts), 0, BITMAP_GETLEN(desc->natts)); - securec_check(erc, "\0", "\0"); - for (int i = 0; i < desc->natts; i++) { - if (!desc->attrs[i]->attisdropped) { - BITMAP_SET(ptrAttrsModify, (desc->attrs[i]->attnum - 1)); - } - } - } - break; - } - default: - break; - } + PrepareAttributeList(plan, rte, rel, fdwState, table, ptrAttrsModify, ixUpd); heap_close(rel, NoLock); - return ((fdwState == nullptr) ? (List*)BitmapSerialize(nullptr, attrsModify, BITMAP_GETLEN(desc->natts)) + return ((fdwState == nullptr) ? (List*)BitmapSerialize(nullptr, attrsModify, BITMAP_GETLEN(desc->natts), ixUpd) : (List*)SerializeFdwState(fdwState)); } @@ -1362,6 +1490,9 @@ static TupleTableSlot* MOTExecForeignInsert( MOTFdwStateSt* fdwState = (MOTFdwStateSt*)resultRelInfo->ri_FdwState; MOT::RC rc = MOT::RC_OK; + // CopyFrom will call MOTExecForeignInsert directly, not through MOTBeginForeignModify. + (void)GetCurrentTransactionId(); + if (MOTAdaptor::m_engine->IsSoftMemoryLimitReached() && fdwState != nullptr) { CleanQueryStatesOnError(fdwState->m_currTxn); } @@ -1388,12 +1519,16 @@ static TupleTableSlot* MOTExecForeignInsert( resultRelInfo->ri_FdwState = fdwState; } + if (IsTxnInAbortState(fdwState->m_currTxn)) { + raiseAbortTxnError(); + } + if ((rc = MOTAdaptor::InsertRow(fdwState, slot)) == MOT::RC_OK) { estate->es_processed++; - if (resultRelInfo->ri_projectReturning) + if (resultRelInfo->ri_projectReturning) { return slot; - else - return nullptr; + } + return nullptr; } else { if (MOT_IS_SEVERE()) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "MOTExecForeignInsert", "Failed to insert row"); @@ -1491,14 +1626,8 @@ static TupleTableSlot* MOTExecForeignDelete( return nullptr; } + // this can happen only on double delete of the same row in the same query if (currRow == nullptr) { - // This case handle multiple updates of the same row in one query - if (fdwState->m_currTxn->IsUpdatedInCurrStmt()) { - return nullptr; - } - elog(ERROR, "MOTExecForeignDelete failed to fetch row"); - CleanQueryStatesOnError(fdwState->m_currTxn); - report_pg_error(((rc == MOT::RC_OK) ? MOT::RC_ERROR : rc)); return nullptr; } @@ -1506,7 +1635,7 @@ static TupleTableSlot* MOTExecForeignDelete( if (resultRelInfo->ri_projectReturning) { MOTAdaptor::UnpackRow( slot, fdwState->m_table, fdwState->m_attrsUsed, const_cast(currRow->GetData())); - ExecStoreVirtualTuple(slot); + (void)ExecStoreVirtualTuple(slot); return slot; } else { estate->es_processed++; @@ -1527,7 +1656,7 @@ static void MOTEndForeignModify(EState* estate, ResultRelInfo* resultRelInfo) { MOTFdwStateSt* fdwState = (MOTFdwStateSt*)resultRelInfo->ri_FdwState; - if (fdwState->m_allocInScan == false) { + if (!fdwState->m_allocInScan) { ReleaseFdwState(fdwState); resultRelInfo->ri_FdwState = NULL; } @@ -1535,8 +1664,14 @@ static void MOTEndForeignModify(EState* estate, ResultRelInfo* resultRelInfo) static void MOTXactCallback(XactEvent event, void* arg) { - int rc = MOT::RC_OK; + if (event == XACT_EVENT_POST_COMMIT_CLEANUP) { + // Nothing to do. XACT_EVENT_POST_COMMIT_CLEANUP is only applicable for JITXactCallback. + return; + } + + MOT::RC rc = MOT::RC_OK; MOT::TxnManager* txn = nullptr; + int saveInterruptHoldoffCount = t_thrd.int_cxt.InterruptHoldoffCount; PG_TRY(); { @@ -1544,15 +1679,18 @@ static void MOTXactCallback(XactEvent event, void* arg) } PG_CATCH(); { + /* + * handle ereport error will reset InterruptHoldoffCount issue, + * if not handle, caller may fail on assert + */ + t_thrd.int_cxt.InterruptHoldoffCount = saveInterruptHoldoffCount; switch (event) { case XACT_EVENT_ABORT: case XACT_EVENT_ROLLBACK_PREPARED: case XACT_EVENT_PREROLLBACK_CLEANUP: - elog(LOG, "Failed to get MOT transaction manager during abort."); return; default: PG_RE_THROW(); - return; } } PG_END_TRY(); @@ -1566,7 +1704,9 @@ static void MOTXactCallback(XactEvent event, void* arg) elog(DEBUG2, "xact_callback event %u, transaction state %u, tid %lu", event, txnState, tid); - if (event == XACT_EVENT_START) { + if (event == XACT_EVENT_STMT_FINISH) { + txn->FinishStatement(); + } else if (event == XACT_EVENT_START) { elog(DEBUG2, "XACT_EVENT_START, tid %lu", tid); if (txnState == MOT::TxnState::TXN_START) { // Double start!!! @@ -1597,13 +1737,28 @@ static void MOTXactCallback(XactEvent event, void* arg) elog(DEBUG2, "XACT_EVENT_COMMIT, tid %lu", tid); + if (!IsTransactionBlock() && txn->IsTxnAborted()) { + elog(DEBUG2, "Implicit transaction aborted by sub-transaction"); + txn->SetTxnState(MOT::TxnState::TXN_ROLLBACK); + return; + } + + if (txn->IsTxnAborted()) { + raiseAbortTxnError(); + } + + if (IsMixedEngineUsed() && t_thrd.xlog_cxt.XactLastRecEnd != InvalidXLogRecPtr) { + elog(DEBUG2, "XACT_EVENT_COMMIT, marking tid %lu as cross engine transaction", tid); + txn->MarkAsCrossEngineTxn(); + } + rc = MOTAdaptor::ValidateCommit(); if (rc != MOT::RC_OK) { elog(DEBUG2, "commit failed"); elog(DEBUG2, "Abort parent transaction from MOT commit, tid %lu", tid); MemoryEreportError(); abortParentTransactionParamsNoDetail(ERRCODE_T_R_SERIALIZATION_FAILURE, - "Commit: could not serialize access due to concurrent update(%d)", + "Commit: could not serialize access due to concurrent update(%u)", txnState); } txn->SetTxnState(MOT::TxnState::TXN_COMMIT); @@ -1613,9 +1768,18 @@ static void MOTXactCallback(XactEvent event, void* arg) return; } + if (txn->IsTxnAborted()) { + elog(DEBUG2, "Implicit transaction aborted by sub-transaction"); + return; + } + MOT_ASSERT(txnState == MOT::TxnState::TXN_COMMIT || txnState == MOT::TxnState::TXN_PREPARE); elog(DEBUG2, "XACT_EVENT_RECORD_COMMIT, tid %lu", tid); + if (MOTAdaptor::IsTxnWriteSetEmpty()) { + return; + } + // Need to get the envelope CSN for cross transaction support. uint64_t csn = MOT::GetCSNManager().GetNextCSN(); if (txnState == MOT::TxnState::TXN_PREPARE) { @@ -1655,6 +1819,12 @@ static void MOTXactCallback(XactEvent event, void* arg) MOTAdaptor::CommitPrepared(csn); } else if (txnState == MOT::TxnState::TXN_START) { elog(DEBUG2, "XACT_EVENT_COMMIT_PREPARED, tid %lu", tid); + + if (IsMixedEngineUsed() && t_thrd.xlog_cxt.XactLastRecEnd != InvalidXLogRecPtr) { + elog(DEBUG2, "XACT_EVENT_COMMIT_PREPARED, marking tid %lu as cross engine transaction", tid); + txn->MarkAsCrossEngineTxn(); + } + // Need to get the envelope CSN for cross transaction support. uint64_t csn = MOT::GetCSNManager().GetNextCSN(); rc = MOTAdaptor::Commit(csn); @@ -1688,6 +1858,60 @@ static void MOTXactCallback(XactEvent event, void* arg) static void MOTSubxactCallback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void* arg) { + MOT::TxnManager* txn = nullptr; + MOT::RC rc = MOT::RC_OK; + bool hasCommitedSubTxn = false; + int savedInterruptHoldoffCount = t_thrd.int_cxt.InterruptHoldoffCount; + PG_TRY(); + { + txn = GetSafeTxn(__FUNCTION__); + } + PG_CATCH(); + { + /* + * handle ereport error will reset InterruptHoldoffCount issue, + * if not handle, caller may fail on assert + */ + t_thrd.int_cxt.InterruptHoldoffCount = savedInterruptHoldoffCount; + switch (event) { + case SUBXACT_EVENT_ABORT_SUB: + return; + default: + PG_RE_THROW(); + } + } + PG_END_TRY(); + + switch (event) { + case SUBXACT_EVENT_START_SUB: + elog(DEBUG2, "Start sub transaction %lu, parent %lu", mySubid, parentSubid); + if (txn->IsTxnAborted()) { + raiseAbortTxnError(); + } + txn->StartSubTransaction(mySubid, u_sess->utils_cxt.XactIsoLevel); + break; + case SUBXACT_EVENT_COMMIT_SUB: + elog(DEBUG2, "Commit sub transaction %lu, parent %lu", mySubid, parentSubid); + if (txn->IsTxnAborted() && txn->HasCommitedSubTxnDDL()) { + raiseAbortTxnError(); + } + txn->CommitSubTransaction(mySubid); + break; + case SUBXACT_EVENT_ABORT_SUB: + elog(DEBUG2, "Abort sub transaction %lu, parent %lu", mySubid, parentSubid); + rc = txn->RollbackSubTransaction(mySubid); + hasCommitedSubTxn = txn->HasCommitedSubTxnDDL(); + if (rc != MOT::RC_OK) { + MOTAdaptor::Rollback(); + txn->SetTxnAborted(); + if (hasCommitedSubTxn) { + txn->SetHasCommitedSubTxnDDL(); + } + } + break; + default: + break; + } return; } @@ -1750,12 +1974,16 @@ static void MOTCheckpointCallback(CheckpointEvent checkpointEvent, uint64_t lsn, } } -/* @MOT - * brief: Validate table definition - * input param @obj: A Obj including infomation to validate when alter tabel and create table. +/* + * @brief: Validate table definition + * @param obj: A Obj including infomation to validate when alter tabel and create table. */ static void MOTValidateTableDef(Node* obj) { + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + if (IsTxnInAbortState(txn)) { + raiseAbortTxnError(); + } ::TransactionId tid = GetCurrentTransactionId(); if (obj == nullptr) { return; @@ -1765,22 +1993,65 @@ static void MOTValidateTableDef(Node* obj) case T_AlterTableStmt: { AlterTableStmt* ats = (AlterTableStmt*)obj; AlterTableCmd* cmd = NULL; - bool allow = false; - if (list_length(ats->cmds) == 1) { - ListCell* cell = list_head(ats->cmds); - cmd = (AlterTableCmd*)lfirst(cell); - if (cmd->subtype == AT_ChangeOwner) { - allow = true; - } else if (cmd->subtype == AT_AddIndex) { - allow = true; + ListCell* lc = nullptr; + foreach (lc, ats->cmds) { + bool allow = true; + cmd = (AlterTableCmd*)lfirst(lc); + switch (cmd->subtype) { + case AT_ChangeOwner: + case AT_AddIndex: + case AT_AddColumn: + case AT_DropColumn: + break; + default: + allow = false; + break; + } + if (allow == false) { + ereport(ERROR, + (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Alter table operation '%s' is not supported for memory table.", + CreateAlterTableCommandTag(cmd->subtype)))); + break; } } - if (allow == false) { - ereport(ERROR, - (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), - errmodule(MOD_MOT), - errmsg("Alter table operation is not supported for memory table."))); + break; + } + case T_AlterForeingTableCmd: { + isMemoryLimitReached(); + AlterForeingTableCmd* cmd = (AlterForeingTableCmd*)obj; + switch (cmd->subtype) { + case AT_AddColumn: + elog(LOG, + "Alter table %s add column %s start", + NameStr(cmd->rel->rd_rel->relname), + ((ColumnDef*)cmd->def)->colname); + (void)MOTAdaptor::AlterTableAddColumn(cmd, tid); + elog(LOG, + "Alter table %s add column %s end", + NameStr(cmd->rel->rd_rel->relname), + ((ColumnDef*)cmd->def)->colname); + break; + case AT_DropColumn: + elog(LOG, "Alter table %s drop column %s start", NameStr(cmd->rel->rd_rel->relname), cmd->name); + (void)MOTAdaptor::AlterTableDropColumn(cmd, tid); + elog(LOG, "Alter table %s drop column %s end", NameStr(cmd->rel->rd_rel->relname), cmd->name); + break; + case AT_UnusableIndex: + ereport(ERROR, + (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Unusable operation is not supported for memory table."))); + break; + default: + break; } + + if (!IsTransactionBlock()) { + txn->SetHasCommitedSubTxnDDL(); + } + break; } case T_CreateForeignTableStmt: { @@ -1792,12 +2063,22 @@ static void MOTValidateTableDef(Node* obj) errmsg("Cannot create MOT tables while incremental checkpoint is enabled."))); } - MOTAdaptor::CreateTable((CreateForeignTableStmt*)obj, tid); + (void)MOTAdaptor::CreateTable((CreateForeignTableStmt*)obj, tid); + + if (!IsTransactionBlock()) { + txn->SetHasCommitedSubTxnDDL(); + } + break; } case T_IndexStmt: { isMemoryLimitReached(); - MOTAdaptor::CreateIndex((IndexStmt*)obj, tid); + (void)MOTAdaptor::CreateIndex((IndexStmt*)obj, tid); + + if (!IsTransactionBlock()) { + txn->SetHasCommitedSubTxnDDL(); + } + break; } case T_ReindexStmt: { @@ -1811,15 +2092,46 @@ static void MOTValidateTableDef(Node* obj) DropForeignStmt* stmt = (DropForeignStmt*)obj; switch (stmt->relkind) { case RELKIND_INDEX: - MOTAdaptor::DropIndex(stmt, tid); + (void)MOTAdaptor::DropIndex(stmt, tid); break; case RELKIND_RELATION: - MOTAdaptor::DropTable(stmt, tid); + (void)MOTAdaptor::DropTable(stmt, tid); break; default: break; } + + if (!IsTransactionBlock()) { + txn->SetHasCommitedSubTxnDDL(); + } + + break; + } + case T_RenameForeingTableCmd: { + RenameForeingTableCmd* cmd = (RenameForeingTableCmd*)obj; + switch (cmd->renameType) { + case OBJECT_COLUMN: + (void)MOTAdaptor::AlterTableRenameColumn(cmd, tid); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Rename operation type %d is not supported for memory table.", cmd->renameType))); + } + + if (!IsTransactionBlock()) { + txn->SetHasCommitedSubTxnDDL(); + } + + break; + } + case T_RenameStmt: { + ereport(ERROR, + (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Rename operation is not supported for memory table."))); break; } default: @@ -1830,6 +2142,10 @@ static void MOTValidateTableDef(Node* obj) static void MOTTruncateForeignTable(TruncateStmt* stmt, Relation rel) { ::TransactionId tid = GetCurrentTransactionId(); + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + if (IsTxnInAbortState(txn)) { + raiseAbortTxnError(); + } MOT::RC rc = MOTAdaptor::TruncateTable(rel, tid); if (rc != MOT::RC_OK) { MOT_LOG_ERROR_STACK("Failed to truncate table"); @@ -1914,6 +2230,10 @@ static void InitMOTHandler() // Register CLOG callback to our recovery manager. MOT::GetRecoveryManager()->SetCommitLogCallback(&GetTransactionStateCallback); MOTAdaptor::m_callbacks_initialized = true; + } else { + elog(WARNING, + "Incremental Checkpoint is enabled, cannot create and operate on MOT tables " + "(MOT does not support incremental checkpoint)"); } } @@ -1948,7 +2268,7 @@ bool MOTCheckpointExists( return false; } - if (checkpointManager->GetId() == MOT::CheckpointControlFile::invalidId) { + if (checkpointManager->GetId() == MOT::CheckpointControlFile::INVALID_ID) { return false; } @@ -1979,446 +2299,35 @@ bool MOTCheckpointExists( return true; } -inline bool IsNotEqualOper(OpExpr* op) +bool MOTValidateLogLevel(const char* logLevelStr) { - switch (op->opno) { - case INT48NEOID: - case BooleanNotEqualOperator: - case 402: - case INT8NEOID: - case INT84NEOID: - case INT4NEOID: - case INT2NEOID: - case 531: - case INT24NEOID: - case INT42NEOID: - case 561: - case 567: - case 576: - case 608: - case 644: - case FLOAT4NEOID: - case 630: - case 5514: - case 643: - case FLOAT8NEOID: - case 713: - case 812: - case 901: - case BPCHARNEOID: - case 1071: - case DATENEOID: - case 1109: - case 1551: - case FLOAT48NEOID: - case FLOAT84NEOID: - case 1321: - case 1331: - case 1501: - case 1586: - case 1221: - case 1202: - case NUMERICNEOID: - case 1785: - case 1805: - case INT28NEOID: - case INT82NEOID: - case 1956: - case 3799: - case TIMESTAMPNEOID: - case 2350: - case 2363: - case 2376: - case 2389: - case 2539: - case 2545: - case 2973: - case 3517: - case 3630: - case 3677: - case 2989: - case 3883: - case 5551: - return true; + return MOT::ValidateLogLevel(logLevelStr); +} - default: - return false; +bool MOTValidateAffinityMode(const char* affinityModeStr) +{ + return MOT::ValidateAffinityMode(affinityModeStr); +} + +bool MOTValidateMemReserveMode(const char* reserveModeStr) +{ + return MOT::ValidateMemReserveMode(reserveModeStr); +} + +bool MOTValidateMemStorePolicy(const char* storePolicyStr) +{ + return MOT::ValidateMemStorePolicy(storePolicyStr); +} + +bool MOTValidateMemAllocPolicy(const char* allocPolicyStr) +{ + return MOT::ValidateMemAllocPolicy(allocPolicyStr); +} + +void MOTCheckTransactionAborted() +{ + if (u_sess->mot_cxt.txn_manager != nullptr && u_sess->mot_cxt.txn_manager->IsTxnAborted() && + u_sess->mot_cxt.txn_manager->HasCommitedSubTxnDDL()) { + raiseAbortTxnError(); } } - -inline void RevertKeyOperation(KEY_OPER& oper) -{ - if (oper == KEY_OPER::READ_KEY_BEFORE) { - oper = KEY_OPER::READ_KEY_AFTER; - } else if (oper == KEY_OPER::READ_KEY_OR_PREV) { - oper = KEY_OPER::READ_KEY_OR_NEXT; - } else if (oper == KEY_OPER::READ_KEY_AFTER) { - oper = KEY_OPER::READ_KEY_BEFORE; - } else if (oper == KEY_OPER::READ_KEY_OR_NEXT) { - oper = KEY_OPER::READ_KEY_OR_PREV; - } - return; -} - -inline bool GetKeyOperation(OpExpr* op, KEY_OPER& oper) -{ - switch (op->opno) { - case FLOAT8EQOID: - case FLOAT4EQOID: - case INT2EQOID: - case INT4EQOID: - case INT8EQOID: - case INT24EQOID: - case INT42EQOID: - case INT84EQOID: - case INT48EQOID: - case INT28EQOID: - case INT82EQOID: - case FLOAT48EQOID: - case FLOAT84EQOID: - case 5513: // INT1EQ - case BPCHAREQOID: - case TEXTEQOID: - case 92: // CHAREQ - case 2536: // timestampVStimestamptz - case 2542: // timestamptzVStimestamp - case 2347: // dateVStimestamp - case 2360: // dateVStimestamptz - case 2373: // timestampVSdate - case 2386: // timestamptzVSdate - case TIMESTAMPEQOID: - oper = KEY_OPER::READ_KEY_EXACT; - break; - case FLOAT8LTOID: - case FLOAT4LTOID: - case INT2LTOID: - case INT4LTOID: - case INT8LTOID: - case INT24LTOID: - case INT42LTOID: - case INT84LTOID: - case INT48LTOID: - case INT28LTOID: - case INT82LTOID: - case FLOAT48LTOID: - case FLOAT84LTOID: - case 5515: // INT1LT - case 1058: // BPCHARLT - case 631: // CHARLT - case TEXTLTOID: - case 2534: // timestampVStimestamptz - case 2540: // timestamptzVStimestamp - case 2345: // dateVStimestamp - case 2358: // dateVStimestamptz - case 2371: // timestampVSdate - case 2384: // timestamptzVSdate - case TIMESTAMPLTOID: - oper = KEY_OPER::READ_KEY_BEFORE; - break; - case FLOAT8LEOID: - case FLOAT4LEOID: - case INT2LEOID: - case INT4LEOID: - case INT8LEOID: - case INT24LEOID: - case INT42LEOID: - case INT84LEOID: - case INT48LEOID: - case INT28LEOID: - case INT82LEOID: - case FLOAT48LEOID: - case FLOAT84LEOID: - case 5516: // INT1LE - case 1059: // BPCHARLE - case 632: // CHARLE - case 665: // TEXTLE - case 2535: // timestampVStimestamptz - case 2541: // timestamptzVStimestamp - case 2346: // dateVStimestamp - case 2359: // dateVStimestamptz - case 2372: // timestampVSdate - case 2385: // timestamptzVSdate - case TIMESTAMPLEOID: - oper = KEY_OPER::READ_KEY_OR_PREV; - break; - case FLOAT8GTOID: - case FLOAT4GTOID: - case INT2GTOID: - case INT4GTOID: - case INT8GTOID: - case INT24GTOID: - case INT42GTOID: - case INT84GTOID: - case INT48GTOID: - case INT28GTOID: - case INT82GTOID: - case FLOAT48GTOID: - case FLOAT84GTOID: - case 5517: // INT1GT - case 1060: // BPCHARGT - case 633: // CHARGT - case TEXTGTOID: // TEXTGT - case 2538: // timestampVStimestamptz - case 2544: // timestamptzVStimestamp - case 2349: // dateVStimestamp - case 2362: // dateVStimestamptz - case 2375: // timestampVSdate - case 2388: // timestamptzVSdate - case TIMESTAMPGTOID: - oper = KEY_OPER::READ_KEY_AFTER; - break; - case FLOAT8GEOID: - case FLOAT4GEOID: - case INT2GEOID: - case INT4GEOID: - case INT8GEOID: - case INT24GEOID: - case INT42GEOID: - case INT84GEOID: - case INT48GEOID: - case INT28GEOID: - case INT82GEOID: - case FLOAT48GEOID: - case FLOAT84GEOID: - case 5518: // INT1GE - case 1061: // BPCHARGE - case 634: // CHARGE - case 667: // TEXTGE - case 2537: // timestampVStimestamptz - case 2543: // timestamptzVStimestamp - case 2348: // dateVStimestamp - case 2361: // dateVStimestamptz - case 2374: // timestampVSdate - case 2387: // timestamptzVSdate - case TIMESTAMPGEOID: - oper = KEY_OPER::READ_KEY_OR_NEXT; - break; - case OID_TEXT_LIKE_OP: - case OID_BPCHAR_LIKE_OP: - oper = KEY_OPER::READ_KEY_LIKE; - break; - default: - oper = KEY_OPER::READ_INVALID; - break; - } - - return (oper != KEY_OPER::READ_INVALID); -} - -bool IsSameRelation(Expr* expr, uint32_t id) -{ - switch (expr->type) { - case T_Param: - case T_Const: { - return false; - } - case T_Var: { - return (((Var*)expr)->varno == id); - } - case T_OpExpr: { - OpExpr* op = (OpExpr*)expr; - bool l = IsSameRelation((Expr*)linitial(op->args), id); - bool r = IsSameRelation((Expr*)lsecond(op->args), id); - return (l || r); - } - case T_FuncExpr: { - FuncExpr* func = (FuncExpr*)expr; - - if (func->funcformat == COERCE_IMPLICIT_CAST || func->funcformat == COERCE_EXPLICIT_CAST) { - return IsSameRelation((Expr*)linitial(func->args), id); - } else if (list_length(func->args) == 0) { - return false; - } else { - return true; - } - } - case T_RelabelType: { - return IsSameRelation(((RelabelType*)expr)->arg, id); - } - default: - return true; - } -} - -bool IsMOTExpr(RelOptInfo* baserel, MOTFdwStateSt* state, MatchIndexArr* marr, Expr* expr, Expr** result, bool setLocal) -{ - /* - * We only support the following operators and data types. - */ - bool isOperatorMOTReady = false; - - switch (expr->type) { - case T_Const: { - if (result != nullptr) - *result = expr; - isOperatorMOTReady = true; - break; - } - - case T_Var: { - if (result != nullptr) - *result = expr; - isOperatorMOTReady = true; - break; - } - case T_Param: { - if (result != nullptr) - *result = expr; - isOperatorMOTReady = true; - break; - } - case T_OpExpr: { - KEY_OPER oper; - OpExpr* op = (OpExpr*)expr; - Expr* l = (Expr*)linitial(op->args); - - if (list_length(op->args) == 1) { - isOperatorMOTReady = IsMOTExpr(baserel, state, marr, l, &l, setLocal); - break; - } - - Expr* r = (Expr*)lsecond(op->args); - isOperatorMOTReady = IsMOTExpr(baserel, state, marr, l, &l, setLocal); - isOperatorMOTReady &= IsMOTExpr(baserel, state, marr, r, &r, setLocal); - - // handles case when column = column|const column|const - if (result != nullptr && isOperatorMOTReady) { - if (IsA(l, Var) && IsA(r, Var) && ((Var*)l)->varno == ((Var*)r)->varno) - isOperatorMOTReady = false; - break; - } - - isOperatorMOTReady &= GetKeyOperation(op, oper); - if (isOperatorMOTReady && marr != nullptr) { - Var* v = nullptr; - Expr* e = nullptr; - - // this covers case when baserel.a = t2.a <==> t2.a = baserel.a both will be of type Var - // we have to choose as Expr t2.a cause it will be replaced later with a Param type - if (IsA(l, Var)) { - if (!IsA(r, Var)) { - if (IsSameRelation(r, ((Var*)l)->varno)) { - isOperatorMOTReady = false; - break; - } - v = (Var*)l; - e = r; - } else { - if (((Var*)l)->varno == ((Var*)r)->varno) { // same relation - return false; - } else if (bms_is_member(((Var*)l)->varno, baserel->relids)) { - v = (Var*)l; - e = r; - } else { - v = (Var*)r; - e = l; - RevertKeyOperation(oper); - } - } - } else if (IsA(r, Var)) { - if (IsSameRelation(l, ((Var*)r)->varno)) { - isOperatorMOTReady = false; - break; - } - v = (Var*)r; - e = l; - RevertKeyOperation(oper); - } else { - isOperatorMOTReady = false; - break; - } - - if (oper == KEY_OPER::READ_KEY_LIKE) { - if (!IsA(e, Const)) - return false; - - // we support only prefix search: 'abc%' or 'abc', the last transforms into equal - Const* c = (Const*)e; - if (DatumGetPointer(c->constvalue) == NULL) - return false; - - int len = 0; - char* s = DatumGetPointer(c->constvalue); - int i = 0; - - if (c->constlen > 0) - len = c->constlen; - else if (c->constlen == -1) { - struct varlena* vs = (struct varlena*)DatumGetPointer(c->constvalue); - s = VARDATA(c->constvalue); - len = VARSIZE_ANY(vs) - VARHDRSZ; - } else if (c->constlen == -2) { - len = strlen(s); - } - - for (; i < len; i++) { - if (s[i] == '%') - break; - - if (s[i] == '_') // we do not support single char pattern - return false; - } - - if (i < len - 1) - return false; - } - isOperatorMOTReady = MOTAdaptor::SetMatchingExpr(state, marr, v->varoattno, oper, e, expr, setLocal); - } - break; - } - case T_FuncExpr: { - FuncExpr* func = (FuncExpr*)expr; - - if (func->funcformat == COERCE_IMPLICIT_CAST || func->funcformat == COERCE_EXPLICIT_CAST) { - isOperatorMOTReady = IsMOTExpr(baserel, state, marr, (Expr*)linitial(func->args), result, setLocal); - } else if (list_length(func->args) == 0) { - isOperatorMOTReady = true; - } - - break; - } - case T_RelabelType: { - isOperatorMOTReady = IsMOTExpr(baserel, state, marr, ((RelabelType*)expr)->arg, result, setLocal); - break; - } - default: { - isOperatorMOTReady = false; - break; - } - } - - return isOperatorMOTReady; -} - -uint16_t MOTTimestampToStr(uintptr_t src, char* destBuf, size_t len) -{ - char* tmp = nullptr; - Timestamp timestamp = DatumGetTimestamp(src); - tmp = DatumGetCString(DirectFunctionCall1(timestamp_out, timestamp)); - errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); - pfree_ext(tmp); - securec_check_ss(erc, "\0", "\0"); - return erc; -} - -uint16_t MOTTimestampTzToStr(uintptr_t src, char* destBuf, size_t len) -{ - char* tmp = nullptr; - TimestampTz timestamp = DatumGetTimestampTz(src); - tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out, timestamp)); - errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); - pfree_ext(tmp); - securec_check_ss(erc, "\0", "\0"); - return erc; -} - -uint16_t MOTDateToStr(uintptr_t src, char* destBuf, size_t len) -{ - char* tmp = nullptr; - DateADT date = DatumGetDateADT(src); - tmp = DatumGetCString(DirectFunctionCall1(date_out, date)); - errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); - pfree_ext(tmp); - securec_check_ss(erc, "\0", "\0"); - return erc; -} diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_error.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_error.cpp index 888c211a5..9cf487340 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_error.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_error.cpp @@ -71,7 +71,7 @@ static const MotErrToPGErrSt MM_ERRCODE_TO_PG[] = { "Can't create index", "Total number of indexes for table %s is greater than the maximum number if indexes allowed %u"}, // RC_TXN_EXCEEDS_MAX_DDLS, - {ERRCODE_FDW_TOO_MANY_DDL_CHANGES_IN_TRANSACTION_NOT_ALLOWED, + {ERRCODE_FDW_TOO_MANY_DDL_CHANGES_IN_TRANSACTION, "Cannot execute statement", "Maximum number of DDLs per transactions reached the maximum %u"}, // RC_UNIQUE_VIOLATION @@ -86,29 +86,40 @@ static const MotErrToPGErrSt MM_ERRCODE_TO_PG[] = { {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_LOCAL_ROW_DELETED {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, + // RC_PRIMARY_SENTINEL_NOT_MAPPED + {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_INSERT_ON_EXIST {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_INDEX_RETRY_INSERT {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_INDEX_DELETE {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, + // RC_GC_INFO_REMOVE + {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_LOCAL_ROW_NOT_VISIBLE {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, // RC_MEMORY_ALLOCATION_ERROR {ERRCODE_OUT_OF_LOGICAL_MEMORY, "Memory is temporarily unavailable", nullptr}, // RC_ILLEGAL_ROW_STATE {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}, - // RC_NULL_VOILATION + // RC_NULL_VIOLATION {ERRCODE_FDW_ERROR, "Null constraint violated", "NULL value cannot be inserted into non-null column %s at table %s"}, // RC_PANIC {ERRCODE_FDW_ERROR, "Critical error", "Critical error: %s"}, + // RC_JIT_SP_EXCEPTION + {ERRCODE_FDW_ERROR, "JIT stored procedure exception thrown", nullptr}, + // RC_TXN_ABORTED + {ERRCODE_FDW_ERROR, "Current transaction is aborted", nullptr}, + // RC_CONCURRENT_MODIFICATION + {ERRCODE_T_R_SERIALIZATION_FAILURE, "Concurrent modification occurred", nullptr}, + // RC_STATEMENT_CANCELED + {ERRCODE_QUERY_CANCELED, "canceling statement due to user request", nullptr}, // RC_NA {ERRCODE_FDW_OPERATION_NOT_SUPPORTED, "A checkpoint is in progress - cannot truncate table.", nullptr}, // RC_MAX_VALUE - {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr} -}; + {ERRCODE_FDW_ERROR, "Unknown error has occurred", nullptr}}; static_assert(sizeof(MM_ERRCODE_TO_PG) / sizeof(MotErrToPGErrSt) == MOT::RC_MAX_VALUE + 1, "Not all MOT engine error codes (RC) is mapped to PG error codes"); @@ -195,7 +206,7 @@ void report_pg_error(MOT::RC rc, void* arg1, void* arg2, void* arg3, void* arg4, (errmodule(MOD_MOT), errcode(err->m_pgErr), errmsg("%s", err->m_msg), - errdetail(err->m_detail, ((MOT::Table*)arg1)->GetTableName(), MAX_NUM_INDEXES))); + errdetail(err->m_detail, ((MOT::Table*)arg1)->GetTableName().c_str(), MAX_NUM_INDEXES))); break; case MOT::RC_TXN_EXCEEDS_MAX_DDLS: ereport(ERROR, @@ -217,10 +228,12 @@ void report_pg_error(MOT::RC rc, void* arg1, void* arg2, void* arg3, void* arg4, ereport(ERROR, (errmodule(MOD_MOT), errcode(err->m_pgErr), errmsg(err->m_msg, (char*)arg1))); break; - // following errors are internal and should not get to an upper layer + // following errors are internal and should not get to an upper layer + case MOT::RC_GC_INFO_REMOVE: case MOT::RC_LOCAL_ROW_FOUND: case MOT::RC_LOCAL_ROW_NOT_FOUND: case MOT::RC_LOCAL_ROW_DELETED: + case MOT::RC_PRIMARY_SENTINEL_NOT_MAPPED: case MOT::RC_INSERT_ON_EXIST: case MOT::RC_INDEX_RETRY_INSERT: case MOT::RC_INDEX_DELETE: diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp new file mode 100644 index 000000000..bcc66dda7 --- /dev/null +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp @@ -0,0 +1,968 @@ +/* + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mot_fdw_helpers.cpp + * MOT Foreign Data Wrapper helpers. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "global.h" +#include "funcapi.h" +#include "catalog/pg_operator.h" +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "utils/date.h" +#include "mot_internal.h" +#include "mot_fdw_helpers.h" +#include "jit_context.h" + +// enable MOT Engine logging facilities +DECLARE_LOGGER(InternalExecutorHelper, FDW) + +bool IsNotEqualOper(OpExpr* op) +{ + switch (op->opno) { + case INT48NEOID: + case BooleanNotEqualOperator: + case 402: + case INT8NEOID: + case INT84NEOID: + case INT4NEOID: + case INT2NEOID: + case 531: + case INT24NEOID: + case INT42NEOID: + case 561: + case 567: + case 576: + case 608: + case 644: + case FLOAT4NEOID: + case 630: + case 5514: + case 643: + case FLOAT8NEOID: + case 713: + case 812: + case 901: + case BPCHARNEOID: + case 1071: + case DATENEOID: + case 1109: + case 1551: + case FLOAT48NEOID: + case FLOAT84NEOID: + case 1321: + case 1331: + case 1501: + case 1586: + case 1221: + case 1202: + case NUMERICNEOID: + case 1785: + case 1805: + case INT28NEOID: + case INT82NEOID: + case 1956: + case 3799: + case TIMESTAMPNEOID: + case 2350: + case 2363: + case 2376: + case 2389: + case 2539: + case 2545: + case 2973: + case 3517: + case 3630: + case 3677: + case 2989: + case 3883: + case 5551: + return true; + + default: + return false; + } +} + +inline void RevertKeyOperation(KEY_OPER& oper) +{ + if (oper == KEY_OPER::READ_KEY_BEFORE) { + oper = KEY_OPER::READ_KEY_AFTER; + } else if (oper == KEY_OPER::READ_KEY_OR_PREV) { + oper = KEY_OPER::READ_KEY_OR_NEXT; + } else if (oper == KEY_OPER::READ_KEY_AFTER) { + oper = KEY_OPER::READ_KEY_BEFORE; + } else if (oper == KEY_OPER::READ_KEY_OR_NEXT) { + oper = KEY_OPER::READ_KEY_OR_PREV; + } + return; +} + +inline bool GetKeyOperation(OpExpr* op, KEY_OPER& oper) +{ + switch (op->opno) { + case FLOAT8EQOID: + case FLOAT4EQOID: + case INT2EQOID: + case INT4EQOID: + case INT8EQOID: + case INT24EQOID: + case INT42EQOID: + case INT84EQOID: + case INT48EQOID: + case INT28EQOID: + case INT82EQOID: + case FLOAT48EQOID: + case FLOAT84EQOID: + case 5513: // INT1EQ + case BPCHAREQOID: + case TEXTEQOID: + case 92: // CHAREQ + case 2536: // timestampVStimestamptz + case 2542: // timestamptzVStimestamp + case 2347: // dateVStimestamp + case 2360: // dateVStimestamptz + case 2373: // timestampVSdate + case 2386: // timestamptzVSdate + case TIMESTAMPEQOID: + oper = KEY_OPER::READ_KEY_EXACT; + break; + case FLOAT8LTOID: + case FLOAT4LTOID: + case INT2LTOID: + case INT4LTOID: + case INT8LTOID: + case INT24LTOID: + case INT42LTOID: + case INT84LTOID: + case INT48LTOID: + case INT28LTOID: + case INT82LTOID: + case FLOAT48LTOID: + case FLOAT84LTOID: + case 5515: // INT1LT + case 1058: // BPCHARLT + case 631: // CHARLT + case TEXTLTOID: + case 2534: // timestampVStimestamptz + case 2540: // timestamptzVStimestamp + case 2345: // dateVStimestamp + case 2358: // dateVStimestamptz + case 2371: // timestampVSdate + case 2384: // timestamptzVSdate + case TIMESTAMPLTOID: + oper = KEY_OPER::READ_KEY_BEFORE; + break; + case FLOAT8LEOID: + case FLOAT4LEOID: + case INT2LEOID: + case INT4LEOID: + case INT8LEOID: + case INT24LEOID: + case INT42LEOID: + case INT84LEOID: + case INT48LEOID: + case INT28LEOID: + case INT82LEOID: + case FLOAT48LEOID: + case FLOAT84LEOID: + case 5516: // INT1LE + case 1059: // BPCHARLE + case 632: // CHARLE + case 665: // TEXTLE + case 2535: // timestampVStimestamptz + case 2541: // timestamptzVStimestamp + case 2346: // dateVStimestamp + case 2359: // dateVStimestamptz + case 2372: // timestampVSdate + case 2385: // timestamptzVSdate + case TIMESTAMPLEOID: + oper = KEY_OPER::READ_KEY_OR_PREV; + break; + case FLOAT8GTOID: + case FLOAT4GTOID: + case INT2GTOID: + case INT4GTOID: + case INT8GTOID: + case INT24GTOID: + case INT42GTOID: + case INT84GTOID: + case INT48GTOID: + case INT28GTOID: + case INT82GTOID: + case FLOAT48GTOID: + case FLOAT84GTOID: + case 5517: // INT1GT + case 1060: // BPCHARGT + case 633: // CHARGT + case TEXTGTOID: // TEXTGT + case 2538: // timestampVStimestamptz + case 2544: // timestamptzVStimestamp + case 2349: // dateVStimestamp + case 2362: // dateVStimestamptz + case 2375: // timestampVSdate + case 2388: // timestamptzVSdate + case TIMESTAMPGTOID: + oper = KEY_OPER::READ_KEY_AFTER; + break; + case FLOAT8GEOID: + case FLOAT4GEOID: + case INT2GEOID: + case INT4GEOID: + case INT8GEOID: + case INT24GEOID: + case INT42GEOID: + case INT84GEOID: + case INT48GEOID: + case INT28GEOID: + case INT82GEOID: + case FLOAT48GEOID: + case FLOAT84GEOID: + case 5518: // INT1GE + case 1061: // BPCHARGE + case 634: // CHARGE + case 667: // TEXTGE + case 2537: // timestampVStimestamptz + case 2543: // timestamptzVStimestamp + case 2348: // dateVStimestamp + case 2361: // dateVStimestamptz + case 2374: // timestampVSdate + case 2387: // timestamptzVSdate + case TIMESTAMPGEOID: + oper = KEY_OPER::READ_KEY_OR_NEXT; + break; + case OID_TEXT_LIKE_OP: + case OID_BPCHAR_LIKE_OP: + oper = KEY_OPER::READ_KEY_LIKE; + break; + default: + oper = KEY_OPER::READ_INVALID; + break; + } + + return (oper != KEY_OPER::READ_INVALID); +} + +static bool IsMismatchDataTypesSupported(Oid columnType, Oid resultDatumType) +{ + if ((IS_INT_TYPE(columnType) && IS_INT_TYPE(resultDatumType)) || + (IS_TIME_TYPE(columnType) && IS_TIME_TYPE(resultDatumType)) || + (IS_CHAR_TYPE(columnType) && IS_CHAR_TYPE(resultDatumType))) { + return true; + } + + return false; +} + +bool IsSameRelation(Expr* expr, uint32_t id) +{ + switch (expr->type) { + case T_Param: + case T_Const: { + return false; + } + case T_Var: { + return (((Var*)expr)->varno == id); + } + case T_OpExpr: { + OpExpr* op = (OpExpr*)expr; + bool l = IsSameRelation((Expr*)linitial(op->args), id); + bool r = IsSameRelation((Expr*)lsecond(op->args), id); + return (l || r); + } + case T_FuncExpr: { + FuncExpr* func = (FuncExpr*)expr; + + if (func->funcformat == COERCE_IMPLICIT_CAST || func->funcformat == COERCE_EXPLICIT_CAST) { + return IsSameRelation((Expr*)linitial(func->args), id); + } else if (list_length(func->args) == 0) { + return false; + } else { + return true; + } + } + case T_RelabelType: { + return IsSameRelation(((RelabelType*)expr)->arg, id); + } + default: + return true; + } +} + +static bool CheckOperationAdjustOperands(RelOptInfo* baserel, Expr* l, Expr* r, Var*& v, Expr*& e, KEY_OPER& oper) +{ + // this covers case when baserel.a = t2.a <==> t2.a = baserel.a both will be of type Var + // we have to choose as Expr t2.a cause it will be replaced later with a Param type + if (IsA(l, Var)) { + if (!IsA(r, Var)) { + if (IsSameRelation(r, ((Var*)l)->varno)) { + return false; + } + v = (Var*)l; + e = r; + } else if (((Var*)l)->varno == ((Var*)r)->varno) { // same relation + return false; + } else if (bms_is_member(((Var*)l)->varno, baserel->relids)) { + v = (Var*)l; + e = r; + } else { + v = (Var*)r; + e = l; + RevertKeyOperation(oper); + } + } else if (IsA(r, Var)) { + if (IsSameRelation(l, ((Var*)r)->varno)) { + return false; + } + v = (Var*)r; + e = l; + RevertKeyOperation(oper); + } else { + return false; + } + + if (oper == KEY_OPER::READ_KEY_LIKE) { + if (!IsA(e, Const)) { + return false; + } + + // we support only prefix search: 'abc%' or 'abc', the last transforms into equal + Const* c = (Const*)e; + if (DatumGetPointer(c->constvalue) == nullptr) { + return false; + } + + int len = 0; + char* s = DatumGetPointer(c->constvalue); + int i = 0; + + if (c->constlen > 0) { + len = c->constlen; + } else if (c->constlen == -1) { + struct varlena* vs = (struct varlena*)DatumGetPointer(c->constvalue); + s = VARDATA(c->constvalue); + len = VARSIZE_ANY(vs) - VARHDRSZ; + } else if (c->constlen == -2) { + len = strlen(s); + } + + for (; i < len; i++) { + if (s[i] == '%') { + break; + } + + if (s[i] == '_') { // we do not support single char pattern + return false; + } + } + + if (i < len - 1) { + return false; + } + } + + return true; +} + +static bool CheckCastFunction(MOTFdwStateSt* state, FuncExpr* func) +{ + Expr* argExpr = (Expr*)linitial(func->args); + if (argExpr && argExpr->type == T_Var) { + Var* var = (Var*)argExpr; + AttrNumber attno = var->varoattno; + if (state->m_table && ((uint32_t)attno < state->m_table->GetFieldCount())) { + MOT::Column* col = state->m_table->GetField(attno); + Oid columnType = ConvertMotColumnTypeToOid(col->m_type); + Oid resultDatumType = func->funcresulttype; + + if (!IsMismatchDataTypesSupported(columnType, resultDatumType)) { + return false; + } + } else { + return false; + } + } + + return true; +} + +bool IsMOTExpr(RelOptInfo* baserel, MOTFdwStateSt* state, MatchIndexArr* marr, Expr* expr, Expr** result, bool setLocal) +{ + /* + * We only support the following operators and data types. + */ + bool isOperatorMOTReady = false; + + switch (expr->type) { + case T_Const: { + if (result != nullptr) + *result = expr; + isOperatorMOTReady = true; + break; + } + + case T_Var: { + if (result != nullptr) + *result = expr; + isOperatorMOTReady = true; + break; + } + case T_Param: { + if (result != nullptr) + *result = expr; + isOperatorMOTReady = true; + break; + } + case T_OpExpr: { + KEY_OPER oper; + OpExpr* op = (OpExpr*)expr; + Expr* l = (Expr*)linitial(op->args); + + if (list_length(op->args) == 1) { + isOperatorMOTReady = IsMOTExpr(baserel, state, marr, l, &l, setLocal); + break; + } + + Expr* r = (Expr*)lsecond(op->args); + isOperatorMOTReady = IsMOTExpr(baserel, state, marr, l, &l, setLocal); + isOperatorMOTReady = (isOperatorMOTReady && IsMOTExpr(baserel, state, marr, r, &r, setLocal)); + // handles case when column = column|const column|const + if (result != nullptr && isOperatorMOTReady) { + isOperatorMOTReady = !(IsA(l, Var) && IsA(r, Var) && ((Var*)l)->varno == ((Var*)r)->varno); + break; + } + + isOperatorMOTReady = (isOperatorMOTReady && GetKeyOperation(op, oper)); + if (isOperatorMOTReady && marr != nullptr) { + Var* v = nullptr; + Expr* e = nullptr; + + isOperatorMOTReady = CheckOperationAdjustOperands(baserel, l, r, v, e, oper); + if (!isOperatorMOTReady) { + break; + } + isOperatorMOTReady = MOTAdaptor::SetMatchingExpr(state, marr, v->varoattno, oper, e, expr, setLocal); + } + break; + } + case T_FuncExpr: { + FuncExpr* func = (FuncExpr*)expr; + + bool isCastFunction = + (func->funcformat == COERCE_IMPLICIT_CAST || func->funcformat == COERCE_EXPLICIT_CAST); + if (isCastFunction) { + if (!CheckCastFunction(state, func)) { + return false; + } + + isOperatorMOTReady = IsMOTExpr(baserel, state, marr, (Expr*)linitial(func->args), nullptr, setLocal); + } else if (list_length(func->args) == 0) { + isOperatorMOTReady = true; + } + + break; + } + case T_RelabelType: { + isOperatorMOTReady = IsMOTExpr(baserel, state, marr, ((RelabelType*)expr)->arg, result, setLocal); + break; + } + default: { + isOperatorMOTReady = false; + break; + } + } + + return isOperatorMOTReady; +} + +uint16_t MOTTimestampToStr(uintptr_t src, char* destBuf, size_t len) +{ + char* tmp = nullptr; + Timestamp timestamp = DatumGetTimestamp(src); + tmp = DatumGetCString(DirectFunctionCall1(timestamp_out, timestamp)); + errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); + pfree_ext(tmp); + securec_check_ss(erc, "\0", "\0"); + return static_cast(erc); +} + +uint16_t MOTTimestampTzToStr(uintptr_t src, char* destBuf, size_t len) +{ + char* tmp = nullptr; + TimestampTz timestamp = DatumGetTimestampTz(src); + tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out, timestamp)); + errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); + pfree_ext(tmp); + securec_check_ss(erc, "\0", "\0"); + return static_cast(erc); +} + +uint16_t MOTDateToStr(uintptr_t src, char* destBuf, size_t len) +{ + char* tmp = nullptr; + DateADT date = DatumGetDateADT(src); + tmp = DatumGetCString(DirectFunctionCall1(date_out, date)); + errno_t erc = snprintf_s(destBuf, len, len - 1, tmp); + pfree_ext(tmp); + securec_check_ss(erc, "\0", "\0"); + return static_cast(erc); +} + +void DestroySession(MOT::SessionContext* sessionContext) +{ + MOT_ASSERT(MOTAdaptor::m_engine); + MOT_LOG_DEBUG("Destroying session context %p, connection_id %u", sessionContext, sessionContext->GetConnectionId()); + + if (u_sess->mot_cxt.jit_session_context_pool) { + JitExec::FreeSessionJitContextPool(u_sess->mot_cxt.jit_session_context_pool); + } + MOT::GetSessionManager()->DestroySessionContext(sessionContext); +} + +// Global map of PG session identification (required for session statistics) +// This approach is safer than saving information in the session context +static pthread_spinlock_t sessionDetailsLock; +typedef std::map> SessionDetailsMap; +static SessionDetailsMap sessionDetailsMap; + +void InitSessionDetailsMap() +{ + (void)pthread_spin_init(&sessionDetailsLock, 0); +} + +void DestroySessionDetailsMap() +{ + (void)pthread_spin_destroy(&sessionDetailsLock); +} + +void RecordSessionDetails() +{ + MOT::SessionId sessionId = u_sess->mot_cxt.session_id; + if (sessionId != INVALID_SESSION_ID) { + (void)pthread_spin_lock(&sessionDetailsLock); + (void)sessionDetailsMap.emplace(sessionId, std::make_pair(t_thrd.proc->pid, t_thrd.proc->myStartTime)); + (void)pthread_spin_unlock(&sessionDetailsLock); + } +} + +void ClearSessionDetails(MOT::SessionId sessionId) +{ + if (sessionId != INVALID_SESSION_ID) { + (void)pthread_spin_lock(&sessionDetailsLock); + SessionDetailsMap::iterator itr = sessionDetailsMap.find(sessionId); + if (itr != sessionDetailsMap.end()) { + (void)sessionDetailsMap.erase(itr); + } + (void)pthread_spin_unlock(&sessionDetailsLock); + } +} + +void ClearCurrentSessionDetails() +{ + ClearSessionDetails(u_sess->mot_cxt.session_id); +} + +void GetSessionDetails(MOT::SessionId sessionId, ::ThreadId* gaussSessionId, pg_time_t* sessionStartTime) +{ + // although we have the PGPROC in the user data of the session context, we prefer not to use + // it due to safety (in some unknown constellation we might hold an invalid pointer) + // it is much safer to save a copy of the two required fields + (void)pthread_spin_lock(&sessionDetailsLock); + SessionDetailsMap::iterator itr = sessionDetailsMap.find(sessionId); + if (itr != sessionDetailsMap.end()) { + *gaussSessionId = itr->second.first; + *sessionStartTime = itr->second.second; + } + (void)pthread_spin_unlock(&sessionDetailsLock); +} + +// provide safe session auto-cleanup in case of missing session closure +// This mechanism relies on the fact that when a session ends, eventually its thread is terminated +// ATTENTION: in thread-pooled envelopes this assumption no longer holds true, since the container thread keeps +// running after the session ends, and a session might run each time on a different thread, so we +// disable this feature, instead we use this mechanism to generate thread-ended event into the MOT Engine +static pthread_key_t sessionCleanupKey; + +static void DestroyJitContextsInPlanCacheList(CachedPlanSource* planSourceList) +{ + if (planSourceList == nullptr) { + return; + } + + // ATTENTION: JIT'ed SP queries are pointed by SPI plans, so in order to avoid double free (once through plan, and + // once through plan of grand-parent invoke), we first nullify all non-top-level contexts. + MOT_LOG_TRACE("Nullifying non top-level contexts in their respective plans"); + CachedPlanSource* psrc = planSourceList; + while (psrc != nullptr) { + if ((psrc->mot_jit_context != nullptr) && JitExec::IsJitSubContextInline(psrc->mot_jit_context)) { + MOT_LOG_TRACE("Found JIT sub-context %p in plan %p: %s", + psrc->mot_jit_context, + psrc, + psrc->mot_jit_context->m_queryString); + psrc->mot_jit_context = nullptr; + } + psrc = psrc->next_saved; + } + MOT_LOG_TRACE("Cleaning up all top-level JIT context objects for current session"); + psrc = planSourceList; + while (psrc != nullptr) { + if (psrc->mot_jit_context != nullptr) { + MOT_LOG_TRACE("Found top-level JIT context %p in plan %p", psrc->mot_jit_context, psrc); + JitExec::DestroyJitContext(psrc->mot_jit_context, true); + psrc->mot_jit_context = nullptr; + } + psrc = psrc->next_saved; + } +} + +void DestroySessionJitContexts() +{ + MOT_LOG_TRACE("Destroying all JIT context objects for current session in first_saved_plan list: %p", + u_sess->pcache_cxt.first_saved_plan); + DestroyJitContextsInPlanCacheList(u_sess->pcache_cxt.first_saved_plan); + + MOT_LOG_TRACE("Destroying all JIT context objects for current session in ungpc_saved_plan list: %p", + u_sess->pcache_cxt.ungpc_saved_plan); + DestroyJitContextsInPlanCacheList(u_sess->pcache_cxt.ungpc_saved_plan); + + MOT_LOG_TRACE("Destroying JIT context object for current session in unnamed_stmt_psrc: %p", + u_sess->pcache_cxt.unnamed_stmt_psrc); + if (u_sess->pcache_cxt.unnamed_stmt_psrc != nullptr) { + CachedPlanSource* psrc = u_sess->pcache_cxt.unnamed_stmt_psrc; + if (psrc->mot_jit_context != nullptr) { + if (JitExec::IsJitSubContextInline(psrc->mot_jit_context)) { + MOT_LOG_TRACE("Found JIT sub-context %p in plan %p: %s", + psrc->mot_jit_context, + psrc, + psrc->mot_jit_context->m_queryString); + } else { + MOT_LOG_TRACE("Found top-level JIT context %p in plan %p", psrc->mot_jit_context, psrc); + JitExec::DestroyJitContext(psrc->mot_jit_context, true); + } + psrc->mot_jit_context = nullptr; + } + } + + MOT_LOG_TRACE("DONE Cleaning up all JIT context objects for current session, JIT context count: %u", + u_sess->mot_cxt.jit_context_count); + MOT_ASSERT(u_sess->mot_cxt.jit_context_count == 0); + JitExec::CleanupJitSourceTxnState(); +} + +static void SessionCleanup(void* key) +{ + MOT_ASSERT(!g_instance.attr.attr_common.enable_thread_pool); + + // in order to ensure session-id cleanup for session 0 we use positive values + MOT::SessionId sessionId = (MOT::SessionId)(((uint64_t)key) - 1); + if (sessionId != INVALID_SESSION_ID) { + MOT_LOG_WARN("Encountered unclosed session %u (missing call to DestroyTxn()?)", (unsigned)sessionId); + ClearSessionDetails(sessionId); + MOT_LOG_DEBUG("SessionCleanup(): Calling DestroySessionJitContext()"); + DestroySessionJitContexts(); + if (MOTAdaptor::m_engine) { + MOT::SessionContext* sessionContext = MOT::GetSessionManager()->GetSessionContext(sessionId); + if (sessionContext != nullptr) { + DestroySession(sessionContext); + } + // since a call to on_proc_exit(destroyTxn) was probably missing, we should also cleanup thread-locals + // pay attention that if we got here it means the thread pool is disabled, so we must ensure thread-locals + // are cleaned up right now. Due to these complexities, onCurrentThreadEnding() was designed to be proof + // for repeated calls. + MOTAdaptor::m_engine->OnCurrentThreadEnding(); + } + } +} + +void InitSessionCleanup() +{ + (void)pthread_key_create(&sessionCleanupKey, SessionCleanup); +} + +void DestroySessionCleanup() +{ + (void)pthread_key_delete(sessionCleanupKey); +} + +void ScheduleSessionCleanup() +{ + (void)pthread_setspecific(sessionCleanupKey, (const void*)(uint64_t)(u_sess->mot_cxt.session_id + 1)); +} + +void CancelSessionCleanup() +{ + (void)pthread_setspecific(sessionCleanupKey, nullptr); +} + +/** @brief Notification from thread pool that a session ended or called from sess_exit callback MOTCleanupSession(). */ +void MOTOnSessionClose() +{ + MOT_LOG_TRACE("Received session close notification (session id: %u, connection id: %u, JIT context count: %u)", + u_sess->mot_cxt.session_id, + u_sess->mot_cxt.connection_id, + u_sess->mot_cxt.jit_context_count); + if (u_sess->mot_cxt.session_id != INVALID_SESSION_ID) { + ClearCurrentSessionDetails(); + MOT_LOG_DEBUG("MOTOnSessionClose(): Calling DestroySessionJitContexts()"); + DestroySessionJitContexts(); + if (!MOTAdaptor::m_engine) { + MOT_LOG_ERROR("MOTOnSessionClose(): MOT engine is not initialized"); + } else { + // initialize thread data - this is ok, it will be cleaned up when thread exits + // avoid throwing errors and ignore them at this phase + (void)EnsureSafeThreadAccess(false); + MOT::SessionContext* sessionContext = u_sess->mot_cxt.session_context; + if (sessionContext == nullptr) { + MOT_LOG_WARN("Received session close notification, but no current session is found. Current session id " + "is %u. Request ignored.", + u_sess->mot_cxt.session_id); + } else { + DestroySession(sessionContext); + } + } + } + + MOT_ASSERT(u_sess->mot_cxt.session_id == INVALID_SESSION_ID); + MOT_ASSERT(u_sess->mot_cxt.session_context == nullptr); + MOT_ASSERT(u_sess->mot_cxt.jit_context_count == 0); +} + +/** @brief Notification from thread pool that a pooled thread ended (only when thread pool is ENABLED). */ +void MOTOnThreadShutdown() +{ + if (!MOTAdaptor::m_initialized) { + return; + } + + MOT_LOG_TRACE("Received thread shutdown notification"); + if (!MOTAdaptor::m_engine) { + MOT_LOG_ERROR("MOTOnThreadShutdown(): MOT engine is not initialized"); + } else { + MOTAdaptor::m_engine->OnCurrentThreadEnding(); + } + knl_thread_mot_init(); // reset all thread locals +} + +/** + * @brief on_proc_exit() callback to handle thread-cleanup - regardless of whether thread pool is enabled or not. + * registration to on_proc_exit() is triggered by first call to EnsureSafeThreadAccess(). + */ +void MOTCleanupThread(int status, Datum ptr) +{ + MOT_ASSERT(g_instance.attr.attr_common.enable_thread_pool); + MOT_LOG_TRACE("Received thread cleanup notification (thread-pool ON, thread id: %u, session id: %u)", + (unsigned)MOTCurrThreadId, + u_sess->mot_cxt.session_id); + + // When FATAL error is thrown, proc_exit doesn't call sess_exit. So we call MOTOnSessionClose() to make sure + // session-level resources are destroyed properly. + MOTOnSessionClose(); + + // when thread pool is used we just cleanup current thread + // this might be a duplicate because thread pool also calls MOTOnThreadShutdown() - this is still ok + // because we guard against repeated calls in MOTEngine::onCurrentThreadEnding() + MOTOnThreadShutdown(); +} + +MOTFdwStateSt* InitializeFdwState(void* fdwState, List** fdwExpr, uint64_t exTableID) +{ + MOTFdwStateSt* state = (MOTFdwStateSt*)palloc0(sizeof(MOTFdwStateSt)); + List* values = (List*)fdwState; + + state->m_allocInScan = true; + state->m_foreignTableId = exTableID; + if (list_length(values) > 0) { + ListCell* cell = list_head(values); + int type = ((Const*)lfirst(cell))->constvalue; + if (type != static_cast(FDW_LIST_TYPE::FDW_LIST_STATE)) { + return state; + } + cell = lnext(cell); + state->m_cmdOper = (CmdType)((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_order = (SortDir)((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_hasForUpdate = (bool)((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_foreignTableId = ((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_numAttrs = ((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_ctidNum = ((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + state->m_numExpr = ((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + + int len = BITMAP_GETLEN(state->m_numAttrs); + state->m_attrsUsed = (uint8_t*)palloc0(len); + state->m_attrsModified = (uint8_t*)palloc0(len); + BitmapDeSerialize(state->m_attrsUsed, len, state->m_hasIndexedColUpdate, &cell); + + if (cell != nullptr) { + state->m_bestIx = &state->m_bestIxBuf; + state->m_bestIx->Deserialize(cell, exTableID); + } + + if (fdwExpr != nullptr && *fdwExpr != nullptr) { + ListCell* c = nullptr; + int i = 0; + + // divide fdw expr to param list and original expr + state->m_remoteCondsOrig = nullptr; + + foreach (c, *fdwExpr) { + if (i < state->m_numExpr) { + i++; + continue; + } else { + state->m_remoteCondsOrig = lappend(state->m_remoteCondsOrig, lfirst(c)); + } + } + + *fdwExpr = list_truncate(*fdwExpr, state->m_numExpr); + } + } + return state; +} + +void* SerializeFdwState(MOTFdwStateSt* state) +{ + List* result = nullptr; + + // set list type to FDW_LIST_TYPE::FDW_LIST_STATE + result = lappend( + result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(FDW_LIST_TYPE::FDW_LIST_STATE), false, true)); + result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_cmdOper), false, true)); + result = lappend(result, makeConst(INT1OID, -1, InvalidOid, 1, Int8GetDatum(state->m_order), false, true)); + result = lappend(result, makeConst(BOOLOID, -1, InvalidOid, 1, BoolGetDatum(state->m_hasForUpdate), false, true)); + result = + lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_foreignTableId), false, true)); + result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_numAttrs), false, true)); + result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_ctidNum), false, true)); + result = lappend(result, makeConst(INT2OID, -1, InvalidOid, 2, Int16GetDatum(state->m_numExpr), false, true)); + int len = BITMAP_GETLEN(state->m_numAttrs); + result = BitmapSerialize(result, state->m_attrsUsed, len, state->m_hasIndexedColUpdate); + + if (state->m_bestIx != nullptr) { + state->m_bestIx->Serialize(&result); + } + ReleaseFdwState(state); + return result; +} + +void ReleaseFdwState(MOTFdwStateSt* state) +{ + CleanCursors(state); + + if (state->m_bestIx && state->m_bestIx != &state->m_bestIxBuf) { + pfree(state->m_bestIx); + } + + if (state->m_remoteCondsOrig != nullptr) { + list_free(state->m_remoteCondsOrig); + } + + if (state->m_attrsUsed != nullptr) { + pfree(state->m_attrsUsed); + } + + if (state->m_attrsModified != nullptr) { + pfree(state->m_attrsModified); + } + + state->m_table = nullptr; + pfree(state); +} + +Oid ConvertMotColumnTypeToOid(MOT::MOT_CATALOG_FIELD_TYPES motColumnType) +{ + Oid typeOid = InvalidOid; + switch (motColumnType) { + case MOT::MOT_TYPE_DECIMAL: + typeOid = NUMERICOID; + break; + + case MOT::MOT_TYPE_VARCHAR: + typeOid = VARCHAROID; + break; + + case MOT::MOT_TYPE_CHAR: + typeOid = CHAROID; + break; + + case MOT::MOT_TYPE_TINY: + typeOid = INT1OID; + break; + + case MOT::MOT_TYPE_SHORT: + typeOid = INT2OID; + break; + + case MOT::MOT_TYPE_INT: + typeOid = INT4OID; + break; + + case MOT::MOT_TYPE_LONG: + typeOid = INT8OID; + break; + + case MOT::MOT_TYPE_FLOAT: + typeOid = FLOAT4OID; + break; + + case MOT::MOT_TYPE_DOUBLE: + typeOid = FLOAT8OID; + break; + + case MOT::MOT_TYPE_DATE: + typeOid = DATEOID; + break; + + case MOT::MOT_TYPE_TIME: + typeOid = TIMEOID; + break; + + case MOT::MOT_TYPE_TIMESTAMP: + typeOid = TIMESTAMPOID; + break; + + case MOT::MOT_TYPE_TIMESTAMPTZ: + typeOid = TIMESTAMPTZOID; + break; + + case MOT::MOT_TYPE_INTERVAL: + typeOid = INTERVALOID; + break; + + case MOT::MOT_TYPE_TIMETZ: + typeOid = TIMETZOID; + break; + + case MOT::MOT_TYPE_BLOB: + typeOid = BLOBOID; + break; + + default: + break; + } + + return typeOid; +} diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.h b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.h new file mode 100644 index 000000000..5afe00bf3 --- /dev/null +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.h @@ -0,0 +1,83 @@ +/* + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mot_fdw_helpers.cpp + * MOT Foreign Data Wrapper helpers. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/fdw_adapter/mot_fdw_helpers.cpp + * + * ------------------------------------------------------------------------- + */ + +#ifndef MOT_FDW_HELPERS_H +#define MOT_FDW_HELPERS_H + +#include "catalog_column_types.h" +#include "nodes/primnodes.h" +#include "global.h" +#include "mot_engine.h" +#include "mot_match_index.h" + +void DestroySession(MOT::SessionContext* sessionContext); +void InitSessionDetailsMap(); +void DestroySessionDetailsMap(); +void RecordSessionDetails(); +void ClearSessionDetails(MOT::SessionId sessionId); +void ClearCurrentSessionDetails(); +void GetSessionDetails(MOT::SessionId sessionId, ::ThreadId* gaussSessionId, pg_time_t* sessionStartTime); +void InitSessionCleanup(); +void DestroySessionCleanup(); +void ScheduleSessionCleanup(); +void CancelSessionCleanup(); +void DestroySessionJitContexts(); +void MOTOnThreadShutdown(); +void MOTCleanupThread(int status, Datum ptr); + +bool IsMOTExpr( + RelOptInfo* baserel, MOTFdwStateSt* state, MatchIndexArr* marr, Expr* expr, Expr** result, bool setLocal); +bool IsNotEqualOper(OpExpr* op); + +uint16_t MOTTimestampToStr(uintptr_t src, char* destBuf, size_t len); +uint16_t MOTTimestampTzToStr(uintptr_t src, char* destBuf, size_t len); +uint16_t MOTDateToStr(uintptr_t src, char* destBuf, size_t len); + +/** + * @brief Initializes MOT query state. + * @param fdwState list of nodes + * @param fdwExpr list of additional nodes added during update query + * @param exTableId PG table id + */ +MOTFdwStateSt* InitializeFdwState(void* fdwState, List** fdwExpr, uint64_t exTableID); + +/** + * @brief Serializes MOT query state into list of nodes. + * @param state MOT query state + */ +void* SerializeFdwState(MOTFdwStateSt* state); + +/** + * @brief Releases MOT query state. + * @param state MOT query state + */ +void ReleaseFdwState(MOTFdwStateSt* state); + +/** + * @brief Converts MOT column type to PG column type . + * @param motColumnType MOT column type + */ +Oid ConvertMotColumnTypeToOid(MOT::MOT_CATALOG_FIELD_TYPES motColumnType); + +#endif /* MOT_FDW_HELPERS_H */ diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.cpp new file mode 100644 index 000000000..9bbb917f1 --- /dev/null +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mot_fdw_snapshot_manager.cpp + * MOT Foreign Data Wrapper snapshot interface. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "global.h" +#include "postgres.h" +#include "access/transam.h" +#include "storage/procarray.h" +#include "mot_fdw_snapshot_manager.h" +#include "knl/knl_session.h" +#include "knl/knl_thread.h" +#include "knl/knl_instance.h" +#include "utils/snapshot.h" +#include "utils/atomic.h" + +// Need to connect with envelope +uint64_t SnapshotManager::GetNextCSN() +{ + return GetCommitCsn(); +} + +uint64_t SnapshotManager::GetCurrentCSN() +{ + return u_sess->utils_cxt.CurrentSnapshot->snapshotcsn; +} + +uint64_t SnapshotManager::GetGcEpoch() +{ + return pg_atomic_read_u64(&g_instance.mot_cxt.shmemVariableCache->nextCommitSeqNo); +} + +void SnapshotManager::SetCSN(uint64_t value) +{ + // No-op for the external snapshot manager. Envelope manages the CSN. +} \ No newline at end of file diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.h b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.h new file mode 100644 index 000000000..c82c3a06a --- /dev/null +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * mot_fdw_snapshot_manager.h + * MOT Foreign Data Wrapper snapshot interface. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/fdw_adapter/mot_fdw_snapshot_manager.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef MOT_FDW_SNAPSHOT_MANAGER_H +#define MOT_FDW_SNAPSHOT_MANAGER_H + +#include "icsn_manager.h" + +class SnapshotManager final : public MOT::ICSNManager { +public: + SnapshotManager() noexcept + {} + + ~SnapshotManager() override + {} + + /** @brief Get the next csn. */ + uint64_t GetNextCSN() override; + + /** @brief Get the current csn. */ + uint64_t GetCurrentCSN() override; + + /** @brief Get the current csn. */ + uint64_t GetGcEpoch() override; + + /** @brief Used to enforce a csn value. */ + void SetCSN(uint64_t value) override; +}; + +#endif /* MOT_FDW_SNAPSHOT_MANAGER_H */ diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.cpp index fb4eedac7..1bdc248ae 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.cpp @@ -31,7 +31,6 @@ #include "access/xlog.h" #include "mot_fdw_xlog.h" #include "mot_engine.h" -#include "recovery_manager.h" #include "miscadmin.h" bool IsValidEntry(uint8 code) @@ -41,7 +40,7 @@ bool IsValidEntry(uint8 code) void RedoTransactionCommit(TransactionId xid, void* arg) { - MOT::GetRecoveryManager()->CommitRecoveredTransaction((uint64_t)xid); + (void)MOT::GetRecoveryManager()->CommitTransaction((uint64_t)xid); } MOT::TxnCommitStatus GetTransactionStateCallback(uint64_t transactionId) @@ -68,7 +67,7 @@ void MOTRedo(XLogReaderState* record) size_t len = XLogRecGetDataLen(record); uint64_t lsn = record->EndRecPtr; if (!IsValidEntry(recordType)) { - elog(ERROR, "MOTRedo: invalid op code %u", recordType); + elog(ERROR, "MOTRedo: invalid op code %" PRIu8, recordType); } if (MOT::GetRecoveryManager()->IsErrorSet() || !MOT::GetRecoveryManager()->ApplyRedoLog(lsn, data, len)) { // we treat errors fatally. @@ -86,7 +85,7 @@ uint64_t XLOGLogger::AddToLog(uint8_t* data, uint32_t size) START_CRIT_SECTION(); XLogBeginInsert(); XLogRegisterData((char*)data, size); - XLogInsert(RM_MOT_ID, MOT_REDO_DATA); + (void)XLogInsert(RM_MOT_ID, MOT_REDO_DATA); END_CRIT_SECTION(); return size; } diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.h b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.h index 612b904c5..4bb4cea3c 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.h +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw_xlog.h @@ -40,9 +40,9 @@ void RedoTransactionCommit(TransactionId xid, void* arg); class XLOGLogger : public MOT::ILogger { public: - inline XLOGLogger() + inline XLOGLogger() noexcept {} - inline ~XLOGLogger() + ~XLOGLogger() {} uint64_t AddToLog(MOT::RedoLogBuffer** redoLogBufferArray, uint32_t size); diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp index 85e2db90d..cca2f11ab 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp @@ -43,6 +43,7 @@ #include "utils/date.h" #include "mot_internal.h" +#include "mot_fdw_helpers.h" #include "row.h" #include "log_statistics.h" #include "spin_lock.h" @@ -65,30 +66,25 @@ #include "jit_statistics.h" #include "gaussdb_config_loader.h" -#define IS_CHAR_TYPE(oid) (oid == VARCHAROID || oid == BPCHAROID || oid == TEXTOID || oid == CLOBOID || oid == BYTEAOID) -#define IS_INT_TYPE(oid) \ - (oid == BOOLOID || oid == CHAROID || oid == INT8OID || oid == INT2OID || oid == INT4OID || oid == FLOAT4OID || \ - oid == FLOAT8OID || oid == INT1OID || oid == DATEOID || oid == TIMEOID || oid == TIMESTAMPOID || \ - oid == TIMESTAMPTZOID) - MOT::MOTEngine* MOTAdaptor::m_engine = nullptr; -static XLOGLogger xlogger; +static XLOGLogger g_xlogger; +static SnapshotManager g_snapshotMgr; // enable MOT Engine logging facilities DECLARE_LOGGER(InternalExecutor, FDW) -/** @brief on_proc_exit() callback for cleaning up current thread - only when thread pool is ENABLED. */ -static void MOTCleanupThread(int status, Datum ptr); - -/** @brief Helper for cleaning up all JIT context objects stored in all CachedPlanSource of the current session. */ -static void DestroySessionJitContexts(); - // in a thread-pooled environment we need to ensure thread-locals are initialized properly -static inline void EnsureSafeThreadAccessInline() +static inline bool EnsureSafeThreadAccessInline(bool throwError = true) { if (MOTCurrThreadId == INVALID_THREAD_ID) { MOT_LOG_DEBUG("Initializing safe thread access for current thread"); - MOT::AllocThreadId(); + if (MOT::AllocThreadId() == INVALID_THREAD_ID) { + MOT_LOG_ERROR("Failed to allocate thread identifier"); + if (throwError) { + ereport(ERROR, (errmodule(MOD_MOT), errmsg("Failed to allocate thread identifier"))); + } + return false; + } // register for cleanup only once - not having a current thread id is the safe indicator we never registered // proc-exit callback for this thread if (g_instance.attr.attr_common.enable_thread_pool) { @@ -97,134 +93,27 @@ static inline void EnsureSafeThreadAccessInline() } } if (MOTCurrentNumaNodeId == MEM_INVALID_NODE) { - MOT::InitCurrentNumaNodeId(); - } - MOT::InitMasstreeThreadinfo(); -} - -extern void EnsureSafeThreadAccess() -{ - EnsureSafeThreadAccessInline(); -} - -static void DestroySession(MOT::SessionContext* sessionContext) -{ - MOT_ASSERT(MOTAdaptor::m_engine); - MOT_LOG_DEBUG("Destroying session context %p, connection_id %u", sessionContext, sessionContext->GetConnectionId()); - - if (u_sess->mot_cxt.jit_session_context_pool) { - JitExec::FreeSessionJitContextPool(u_sess->mot_cxt.jit_session_context_pool); - } - MOT::GetSessionManager()->DestroySessionContext(sessionContext); -} - -// Global map of PG session identification (required for session statistics) -// This approach is safer than saving information in the session context -static pthread_spinlock_t sessionDetailsLock; -typedef std::map> SessionDetailsMap; -static SessionDetailsMap sessionDetailsMap; - -static void InitSessionDetailsMap() -{ - pthread_spin_init(&sessionDetailsLock, 0); -} - -static void DestroySessionDetailsMap() -{ - pthread_spin_destroy(&sessionDetailsLock); -} - -static void RecordSessionDetails() -{ - MOT::SessionId sessionId = u_sess->mot_cxt.session_id; - if (sessionId != INVALID_SESSION_ID) { - pthread_spin_lock(&sessionDetailsLock); - sessionDetailsMap.emplace(sessionId, std::make_pair(t_thrd.proc->pid, t_thrd.proc->myStartTime)); - pthread_spin_unlock(&sessionDetailsLock); - } -} - -static void ClearSessionDetails(MOT::SessionId sessionId) -{ - if (sessionId != INVALID_SESSION_ID) { - pthread_spin_lock(&sessionDetailsLock); - SessionDetailsMap::iterator itr = sessionDetailsMap.find(sessionId); - if (itr != sessionDetailsMap.end()) { - sessionDetailsMap.erase(itr); - } - pthread_spin_unlock(&sessionDetailsLock); - } -} - -inline void ClearCurrentSessionDetails() -{ - ClearSessionDetails(u_sess->mot_cxt.session_id); -} - -static void GetSessionDetails(MOT::SessionId sessionId, ::ThreadId* gaussSessionId, pg_time_t* sessionStartTime) -{ - // although we have the PGPROC in the user data of the session context, we prefer not to use - // it due to safety (in some unknown constellation we might hold an invalid pointer) - // it is much safer to save a copy of the two required fields - pthread_spin_lock(&sessionDetailsLock); - SessionDetailsMap::iterator itr = sessionDetailsMap.find(sessionId); - if (itr != sessionDetailsMap.end()) { - *gaussSessionId = itr->second.first; - *sessionStartTime = itr->second.second; - } - pthread_spin_unlock(&sessionDetailsLock); -} - -// provide safe session auto-cleanup in case of missing session closure -// This mechanism relies on the fact that when a session ends, eventually its thread is terminated -// ATTENTION: in thread-pooled envelopes this assumption no longer holds true, since the container thread keeps -// running after the session ends, and a session might run each time on a different thread, so we -// disable this feature, instead we use this mechanism to generate thread-ended event into the MOT Engine -static pthread_key_t sessionCleanupKey; - -static void SessionCleanup(void* key) -{ - MOT_ASSERT(!g_instance.attr.attr_common.enable_thread_pool); - - // in order to ensure session-id cleanup for session 0 we use positive values - MOT::SessionId sessionId = (MOT::SessionId)(((uint64_t)key) - 1); - if (sessionId != INVALID_SESSION_ID) { - MOT_LOG_WARN("Encountered unclosed session %u (missing call to DestroyTxn()?)", (unsigned)sessionId); - ClearSessionDetails(sessionId); - MOT_LOG_DEBUG("SessionCleanup(): Calling DestroySessionJitContext()"); - DestroySessionJitContexts(); - if (MOTAdaptor::m_engine) { - MOT::SessionContext* sessionContext = MOT::GetSessionManager()->GetSessionContext(sessionId); - if (sessionContext != nullptr) { - DestroySession(sessionContext); + if (!MOT::InitCurrentNumaNodeId()) { + MOT_LOG_ERROR("Failed to allocate NUMA node identifier"); + if (throwError) { + ereport(ERROR, (errmodule(MOD_MOT), errmsg("Failed to allocate NUMA node identifier"))); } - // since a call to on_proc_exit(destroyTxn) was probably missing, we should also cleanup thread-locals - // pay attention that if we got here it means the thread pool is disabled, so we must ensure thread-locals - // are cleaned up right now. Due to these complexities, onCurrentThreadEnding() was designed to be proof - // for repeated calls. - MOTAdaptor::m_engine->OnCurrentThreadEnding(); + return false; } } + if (!MOT::InitMasstreeThreadinfo()) { + MOT_LOG_ERROR("Failed to initialize thread-local masstree info"); + if (throwError) { + ereport(ERROR, (errmodule(MOD_MOT), errmsg("Failed to initialize thread-local masstree info"))); + } + return false; + } + return true; } -static void InitSessionCleanup() +extern bool EnsureSafeThreadAccess(bool throwError /* = true */) { - pthread_key_create(&sessionCleanupKey, SessionCleanup); -} - -static void DestroySessionCleanup() -{ - pthread_key_delete(sessionCleanupKey); -} - -static void ScheduleSessionCleanup() -{ - pthread_setspecific(sessionCleanupKey, (const void*)(uint64_t)(u_sess->mot_cxt.session_id + 1)); -} - -static void CancelSessionCleanup() -{ - pthread_setspecific(sessionCleanupKey, nullptr); + return EnsureSafeThreadAccessInline(throwError); } static GaussdbConfigLoader* gaussdbConfigLoader = nullptr; @@ -268,7 +157,7 @@ void MOTAdaptor::Init() } if (!m_engine->LoadConfig()) { - m_engine->RemoveConfigLoader(gaussdbConfigLoader); + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); delete gaussdbConfigLoader; gaussdbConfigLoader = nullptr; MOT::MOTEngine::DestroyInstance(); @@ -285,17 +174,17 @@ void MOTAdaptor::Init() if ((g_instance.attr.attr_memory.max_process_memory < (int32)maxReserveMemoryKb) || ((g_instance.attr.attr_memory.max_process_memory - maxReserveMemoryKb) < MIN_DYNAMIC_PROCESS_MEMORY)) { // we allow one extreme case: GaussDB is configured to its limit, and zero memory is left for us - if (maxReserveMemoryKb <= motCfg.MOT_MIN_MEMORY_USAGE_MB * KILO_BYTE) { + if (maxReserveMemoryKb <= MOT::MOTConfiguration::MOT_MIN_MEMORY_USAGE_MB * KILO_BYTE) { MOT_LOG_WARN("Allowing MOT to work in minimal memory mode"); } else { - m_engine->RemoveConfigLoader(gaussdbConfigLoader); + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); delete gaussdbConfigLoader; gaussdbConfigLoader = nullptr; MOT::MOTEngine::DestroyInstance(); elog(FATAL, "The value of pre-reserved memory for MOT engine is not reasonable: " "Request for a maximum of %" PRIu64 " KB global memory, and %" PRIu64 - " KB session memory (total of %" PRIu64 " KB) is invalid since max_process_memory is %u KB", + " KB session memory (total of %" PRIu64 " KB) is invalid since max_process_memory is %d KB", globalMemoryKb, localMemoryKb, maxReserveMemoryKb, @@ -304,7 +193,7 @@ void MOTAdaptor::Init() } if (!m_engine->Initialize()) { - m_engine->RemoveConfigLoader(gaussdbConfigLoader); + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); delete gaussdbConfigLoader; gaussdbConfigLoader = nullptr; MOT::MOTEngine::DestroyInstance(); @@ -312,7 +201,7 @@ void MOTAdaptor::Init() } if (!JitExec::JitStatisticsProvider::CreateInstance()) { - m_engine->RemoveConfigLoader(gaussdbConfigLoader); + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); delete gaussdbConfigLoader; gaussdbConfigLoader = nullptr; MOT::MOTEngine::DestroyInstance(); @@ -320,10 +209,17 @@ void MOTAdaptor::Init() } // make sure current thread is cleaned up properly when thread pool is enabled - EnsureSafeThreadAccessInline(); + // avoid throwing errors on failure + if (!EnsureSafeThreadAccessInline(false)) { + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); + delete gaussdbConfigLoader; + gaussdbConfigLoader = nullptr; + MOT::MOTEngine::DestroyInstance(); + elog(FATAL, "Failed to initialize thread-local data."); + } if (motCfg.m_enableRedoLog && motCfg.m_loggerType == MOT::LoggerType::EXTERNAL_LOGGER) { - m_engine->GetRedoLogHandler()->SetLogger(&xlogger); + m_engine->GetRedoLogHandler()->SetLogger(&g_xlogger); m_engine->GetRedoLogHandler()->SetWalWakeupFunc(WakeupWalWriter); } @@ -332,6 +228,10 @@ void MOTAdaptor::Init() InitSessionCleanup(); } InitKeyOperStateMachine(); + + MOT_LOG_INFO("Switching to External snapshot manager"); + m_engine->SetCSNManager(&g_snapshotMgr); + m_initialized = true; } @@ -354,12 +254,13 @@ void MOTAdaptor::Destroy() } DestroySessionDetailsMap(); if (gaussdbConfigLoader != nullptr) { - m_engine->RemoveConfigLoader(gaussdbConfigLoader); + (void)m_engine->RemoveConfigLoader(gaussdbConfigLoader); delete gaussdbConfigLoader; gaussdbConfigLoader = nullptr; } - EnsureSafeThreadAccessInline(); + // avoid throwing errors and ignore them at this phase + (void)EnsureSafeThreadAccessInline(false); MOT::MOTEngine::DestroyInstance(); m_engine = nullptr; knl_thread_mot_init(); // reset all thread-locals, mandatory for standby switch-over @@ -410,89 +311,12 @@ MOT::TxnManager* MOTAdaptor::InitTxnManager( } u_sess->mot_cxt.txn_manager = session_ctx->GetTxnManager(); - elog(DEBUG1, "Init TXN_MAN for thread %u", MOTCurrThreadId); + elog(DEBUG1, "Init TXN_MAN for thread %" PRIu16, MOTCurrThreadId); } return u_sess->mot_cxt.txn_manager; } -static void DestroySessionJitContexts() -{ - // we must release all JIT context objects associated with this session now. - // it seems that when thread pool is disabled, all cached plan sources for the session are not - // released explicitly, but rather implicitly as part of the release of the memory context of the session. - // in any case, we guard against repeated destruction of the JIT context by nullifying it - MOT_LOG_DEBUG("Cleaning up all JIT context objects for current session"); - CachedPlanSource* psrc = u_sess->pcache_cxt.first_saved_plan; - while (psrc != nullptr) { - if (psrc->mot_jit_context != nullptr) { - MOT_LOG_DEBUG("DestroySessionJitContexts(): Calling DestroyJitContext(%p)", psrc->mot_jit_context); - JitExec::DestroyJitContext(psrc->mot_jit_context); - psrc->mot_jit_context = nullptr; - } - psrc = psrc->next_saved; - } - MOT_LOG_DEBUG("DONE Cleaning up all JIT context objects for current session"); -} - -/** @brief Notification from thread pool that a session ended (only when thread pool is ENABLED). */ -extern void MOTOnSessionClose() -{ - MOT_LOG_TRACE("Received session close notification (current session id: %u, current connection id: %u)", - u_sess->mot_cxt.session_id, - u_sess->mot_cxt.connection_id); - if (u_sess->mot_cxt.session_id != INVALID_SESSION_ID) { - ClearCurrentSessionDetails(); - MOT_LOG_DEBUG("MOTOnSessionClose(): Calling DestroySessionJitContexts()"); - DestroySessionJitContexts(); - if (!MOTAdaptor::m_engine) { - MOT_LOG_ERROR("MOTOnSessionClose(): MOT engine is not initialized"); - } else { - EnsureSafeThreadAccessInline(); // this is ok, it wil be cleaned up when thread exits - MOT::SessionContext* sessionContext = u_sess->mot_cxt.session_context; - if (sessionContext == nullptr) { - MOT_LOG_WARN("Received session close notification, but no current session is found. Current session id " - "is %u. Request ignored.", - u_sess->mot_cxt.session_id); - } else { - DestroySession(sessionContext); - MOT_ASSERT(u_sess->mot_cxt.session_id == INVALID_SESSION_ID); - } - } - } -} - -/** @brief Notification from thread pool that a pooled thread ended (only when thread pool is ENABLED). */ -static void MOTOnThreadShutdown() -{ - if (!MOTAdaptor::m_initialized) { - return; - } - - MOT_LOG_TRACE("Received thread shutdown notification"); - if (!MOTAdaptor::m_engine) { - MOT_LOG_ERROR("MOTOnThreadShutdown(): MOT engine is not initialized"); - } else { - MOTAdaptor::m_engine->OnCurrentThreadEnding(); - } - knl_thread_mot_init(); // reset all thread locals -} - -/** - * @brief on_proc_exit() callback to handle thread-cleanup - regardless of whether thread pool is enabled or not. - * registration to on_proc_exit() is triggered by first call to EnsureSafeThreadAccessInline(). - */ -static void MOTCleanupThread(int status, Datum ptr) -{ - MOT_ASSERT(g_instance.attr.attr_common.enable_thread_pool); - MOT_LOG_TRACE("Received thread cleanup notification (thread-pool ON)"); - - // when thread pool is used we just cleanup current thread - // this might be a duplicate because thread pool also calls MOTOnThreadShutdown() - this is still ok - // because we guard against repeated calls in MOTEngine::onCurrentThreadEnding() - MOTOnThreadShutdown(); -} - void MOTAdaptor::DestroyTxn(int status, Datum ptr) { MOT_ASSERT(!g_instance.attr.attr_common.enable_thread_pool); @@ -512,8 +336,10 @@ void MOTAdaptor::DestroyTxn(int status, Datum ptr) if (session != MOT_GET_CURRENT_SESSION_CONTEXT()) { MOT_LOG_WARN("Ignoring request to delete session context: already deleted"); } else if (session != nullptr) { - elog(DEBUG1, "Destroy SessionContext, connection_id = %u \n", session->GetConnectionId()); - EnsureSafeThreadAccessInline(); // may be accessed from new thread pool worker + elog(DEBUG1, "Destroy SessionContext, connection_id = %u", session->GetConnectionId()); + // initialize thread data, since this call may be accessed from new thread pool worker + // avoid throwing errors and ignore them at this phase + (void)EnsureSafeThreadAccessInline(false); MOT::GcManager* gc = MOT_GET_CURRENT_SESSION_CONTEXT()->GetTxnManager()->GetGcSession(); if (gc != nullptr) { gc->GcEndTxn(); @@ -527,7 +353,7 @@ void MOTAdaptor::DestroyTxn(int status, Datum ptr) MOT::RC MOTAdaptor::ValidateCommit() { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); if (!IS_PGXC_COORDINATOR) { return txn->ValidateCommit(); @@ -539,7 +365,7 @@ MOT::RC MOTAdaptor::ValidateCommit() void MOTAdaptor::RecordCommit(uint64_t csn) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetCommitSequenceNumber(csn); if (!IS_PGXC_COORDINATOR) { @@ -551,7 +377,7 @@ void MOTAdaptor::RecordCommit(uint64_t csn) MOT::RC MOTAdaptor::Commit(uint64_t csn) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetCommitSequenceNumber(csn); if (!IS_PGXC_COORDINATOR) { @@ -562,9 +388,19 @@ MOT::RC MOTAdaptor::Commit(uint64_t csn) } } +bool MOTAdaptor::IsTxnWriteSetEmpty() +{ + (void)EnsureSafeThreadAccessInline(); + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + if (txn->m_txnDdlAccess->Size() > 0 or txn->m_accessMgr->Size() > 0) { + return false; + } + return true; +} + void MOTAdaptor::EndTransaction() { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); // Nothing to do in coordinator if (!IS_PGXC_COORDINATOR) { @@ -574,7 +410,7 @@ void MOTAdaptor::EndTransaction() void MOTAdaptor::Rollback() { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); if (!IS_PGXC_COORDINATOR) { txn->Rollback(); @@ -585,7 +421,7 @@ void MOTAdaptor::Rollback() MOT::RC MOTAdaptor::Prepare() { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); if (!IS_PGXC_COORDINATOR) { return txn->Prepare(); @@ -597,7 +433,7 @@ MOT::RC MOTAdaptor::Prepare() void MOTAdaptor::CommitPrepared(uint64_t csn) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetCommitSequenceNumber(csn); if (!IS_PGXC_COORDINATOR) { @@ -609,7 +445,7 @@ void MOTAdaptor::CommitPrepared(uint64_t csn) void MOTAdaptor::RollbackPrepared() { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); if (!IS_PGXC_COORDINATOR) { txn->RollbackPrepared(); @@ -620,7 +456,7 @@ void MOTAdaptor::RollbackPrepared() MOT::RC MOTAdaptor::InsertRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); uint8_t* newRowData = nullptr; fdwState->m_currTxn->SetTransactionId(fdwState->m_txnId); MOT::Table* table = fdwState->m_table; @@ -643,8 +479,10 @@ MOT::RC MOTAdaptor::InsertRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot) MOT::RC MOTAdaptor::UpdateRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot, MOT::Row* currRow) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::RC rc; + MOT::TxnIxColUpdate colUpd( + fdwState->m_table, fdwState->m_currTxn, fdwState->m_attrsModified, fdwState->m_hasIndexedColUpdate); do { fdwState->m_currTxn->SetTransactionId(fdwState->m_txnId); @@ -652,11 +490,25 @@ MOT::RC MOTAdaptor::UpdateRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot, MOT if (rc != MOT::RC::RC_OK) { break; } - uint8_t* rowData = const_cast(currRow->GetData()); + MOT::Row* currDraft = fdwState->m_currTxn->GetLastAccessedDraft(); + if (unlikely(fdwState->m_hasIndexedColUpdate != MOT::UpdateIndexColumnType::UPDATE_COLUMN_NONE)) { + rc = colUpd.InitAndBuildOldKeys(currDraft); + if (rc != MOT::RC::RC_OK) { + break; + } + } + uint8_t* rowData = const_cast(currDraft->GetData()); PackUpdateRow(slot, fdwState->m_table, fdwState->m_attrsModified, rowData); MOT::BitmapSet modified_columns(fdwState->m_attrsModified, fdwState->m_table->GetFieldCount() - 1); - - rc = fdwState->m_currTxn->OverwriteRow(currRow, modified_columns); + if (unlikely(fdwState->m_hasIndexedColUpdate)) { + rc = colUpd.FilterColumnUpdate(currDraft); + if (rc != MOT::RC::RC_OK) { + break; + } + rc = fdwState->m_currTxn->UpdateRow(modified_columns, &colUpd); + } else { + rc = fdwState->m_currTxn->OverwriteRow(currDraft, modified_columns); + } } while (0); return rc; @@ -664,12 +516,21 @@ MOT::RC MOTAdaptor::UpdateRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot, MOT MOT::RC MOTAdaptor::DeleteRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); fdwState->m_currTxn->SetTransactionId(fdwState->m_txnId); MOT::RC rc = fdwState->m_currTxn->DeleteLastRow(); return rc; } +bool MOTAdaptor::IsColumnIndexed(int16_t colId, MOT::Table* table) +{ + MOT::Column* col = table->GetField(colId); + if (col != nullptr && col->IsUsedByIndex()) { + return true; + } + return false; +} + // NOTE: colId starts from 1 bool MOTAdaptor::SetMatchingExpr( MOTFdwStateSt* state, MatchIndexArr* marr, int16_t colId, KEY_OPER op, Expr* expr, Expr* parent, bool set_local) @@ -705,8 +566,9 @@ MatchIndex* MOTAdaptor::GetBestMatchIndex(MOTFdwStateSt* festate, MatchIndexArr* double cost = marr->m_idx[i]->GetCost(numClauses); if (cost < bestCost) { if (bestI < MAX_NUM_INDEXES) { - if (marr->m_idx[i]->GetNumMatchedCols() < marr->m_idx[bestI]->GetNumMatchedCols()) + if (marr->m_idx[i]->GetNumMatchedCols() < marr->m_idx[bestI]->GetNumMatchedCols()) { continue; + } } bestCost = cost; bestI = i; @@ -727,13 +589,15 @@ MatchIndex* MOTAdaptor::GetBestMatchIndex(MOTFdwStateSt* festate, MatchIndexArr* if (j > 0 && best->m_opers[k][j - 1] != KEY_OPER::READ_KEY_EXACT && !list_member(festate->m_localConds, best->m_parentColMatch[k][j])) { - if (setLocal) + if (setLocal) { festate->m_localConds = lappend(festate->m_localConds, best->m_parentColMatch[k][j]); + } } } else if (!list_member(festate->m_localConds, best->m_parentColMatch[k][j]) && !list_member(best->m_remoteCondsOrig, best->m_parentColMatch[k][j])) { - if (setLocal) + if (setLocal) { festate->m_localConds = lappend(festate->m_localConds, best->m_parentColMatch[k][j]); + } best->m_colMatch[k][j] = nullptr; best->m_parentColMatch[k][j] = nullptr; } @@ -781,7 +645,7 @@ void MOTAdaptor::OpenCursor(Relation rel, MOTFdwStateSt* festate) bool forwardDirection = true; bool found = false; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); // GetTableByExternalId cannot return nullptr at this stage, because it is protected by envelope's table lock. festate->m_table = festate->m_currTxn->GetTableByExternalId(rel->rd_id); @@ -790,57 +654,39 @@ void MOTAdaptor::OpenCursor(Relation rel, MOTFdwStateSt* festate) // this scan all keys case // we need to open both cursors on start and end to prevent // infinite scan in case "insert into table A ... as select * from table A ... - if (festate->m_bestIx == nullptr) { - int fIx, bIx; - uint8_t* buf = nullptr; + if (festate->m_bestIx == nullptr || festate->m_bestIx->m_fullScan) { // assumption that primary index cannot be changed, can take it from // table and not look on ddl_access - MOT::Index* ix = festate->m_table->GetPrimaryIndex(); + MOT::Index* ix = festate->m_bestIx ? festate->m_bestIx->m_ix : festate->m_table->GetPrimaryIndex(); uint16_t keyLength = ix->GetKeyLength(); - if (festate->m_order == SORTDIR_ENUM::SORTDIR_ASC) { - fIx = 0; - bIx = 1; + if (festate->m_order == SortDir::SORTDIR_ASC) { + festate->m_cursor[0] = ix->Begin(festate->m_currTxn->GetThdId()); festate->m_forwardDirectionScan = true; + festate->m_cursor[1] = nullptr; } else { - fIx = 1; - bIx = 0; + festate->m_stateKey[0].InitKey(keyLength); + uint8_t* buf = festate->m_stateKey[0].GetKeyBuf(); + errno_t erc = memset_s(buf, keyLength, 0xff, keyLength); + securec_check(erc, "\0", "\0"); + festate->m_cursor[0] = + ix->Search(&festate->m_stateKey[0], false, false, festate->m_currTxn->GetThdId(), found); festate->m_forwardDirectionScan = false; + festate->m_cursor[1] = nullptr; } - - festate->m_cursor[fIx] = festate->m_table->Begin(festate->m_currTxn->GetThdId()); - - festate->m_stateKey[bIx].InitKey(keyLength); - buf = festate->m_stateKey[bIx].GetKeyBuf(); - errno_t erc = memset_s(buf, keyLength, 0xff, keyLength); - securec_check(erc, "\0", "\0"); - festate->m_cursor[bIx] = - ix->Search(&festate->m_stateKey[bIx], false, false, festate->m_currTxn->GetThdId(), found); break; } for (int i = 0; i < 2; i++) { if (i == 1 && festate->m_bestIx->m_end < 0) { - if (festate->m_forwardDirectionScan) { - uint8_t* buf = nullptr; - MOT::Index* ix = festate->m_bestIx->m_ix; - uint16_t keyLength = ix->GetKeyLength(); - - festate->m_stateKey[1].InitKey(keyLength); - buf = festate->m_stateKey[1].GetKeyBuf(); - errno_t erc = memset_s(buf, keyLength, 0xff, keyLength); - securec_check(erc, "\0", "\0"); - festate->m_cursor[1] = - ix->Search(&festate->m_stateKey[1], false, false, festate->m_currTxn->GetThdId(), found); - } else { - festate->m_cursor[1] = festate->m_bestIx->m_ix->Begin(festate->m_currTxn->GetThdId()); - } + festate->m_cursor[1] = nullptr; break; } KEY_OPER oper = (i == 0 ? festate->m_bestIx->m_ixOpers[0] : festate->m_bestIx->m_ixOpers[1]); - forwardDirection = ((oper & ~KEY_OPER_PREFIX_BITMASK) < KEY_OPER::READ_KEY_OR_PREV); + forwardDirection = ((static_cast(oper) & ~KEY_OPER_PREFIX_BITMASK) < + static_cast(KEY_OPER::READ_KEY_OR_PREV)); CreateKeyBuffer(rel, festate, i); @@ -878,7 +724,7 @@ void MOTAdaptor::OpenCursor(Relation rel, MOTFdwStateSt* festate) break; default: - elog(INFO, "Invalid key operation: %u", oper); + elog(INFO, "Invalid key operation: %" PRIu8, static_cast(oper)); break; } @@ -893,6 +739,11 @@ void MOTAdaptor::OpenCursor(Relation rel, MOTFdwStateSt* festate) } } } while (0); + for (int i = 0; i < 2; i++) { + if (festate->m_cursor[i] != nullptr) { + festate->m_currTxn->m_queryState[(uint64_t)festate->m_cursor[i]] = (uint64_t)(festate->m_cursor[i]); + } + } } static void VarLenFieldType( @@ -913,7 +764,7 @@ static void VarLenFieldType( } /* fall through */ case 'e': -#ifdef USE_ASSERT_CHECKING +#ifdef MOT_SUPPORT_TEXT_FIELD if (typoid == TEXTOID) *typeLen = colLen = MAX_VARCHAR_LEN; #endif @@ -1023,7 +874,7 @@ static MOT::RC TableFieldType( return res; } -void MOTAdaptor::ValidateCreateIndex(IndexStmt* stmt, MOT::Table* table, MOT::TxnManager* txn) +static void ValidateCreateIndex(IndexStmt* stmt, MOT::Table* table, MOT::TxnManager* txn) { if (stmt->primary) { if (!table->IsTableEmpty(txn->GetThdId())) { @@ -1032,19 +883,16 @@ void MOTAdaptor::ValidateCreateIndex(IndexStmt* stmt, MOT::Table* table, MOT::Tx errcode(ERRCODE_FDW_ERROR), errmsg( "Table %s is not empty, create primary index is not allowed", table->GetTableName().c_str()))); - return; } - } else if (table->GetNumIndexes() == MAX_NUM_INDEXES) { + } else if (table->GetNumIndexes() >= MAX_NUM_INDEXES) { ereport(ERROR, (errmodule(MOD_MOT), errcode(ERRCODE_FDW_TOO_MANY_INDEXES), errmsg("Can not create index, max number of indexes %u reached", MAX_NUM_INDEXES))); - return; } if (strcmp(stmt->accessMethod, "btree") != 0) { ereport(ERROR, (errmodule(MOD_MOT), errmsg("MOT supports indexes of type BTREE only (btree or btree_art)"))); - return; } if (list_length(stmt->indexParams) > (int)MAX_KEY_COLUMNS) { @@ -1054,14 +902,13 @@ void MOTAdaptor::ValidateCreateIndex(IndexStmt* stmt, MOT::Table* table, MOT::Tx errmsg("Can't create index"), errdetail( "Number of columns exceeds %d max allowed %u", list_length(stmt->indexParams), MAX_KEY_COLUMNS))); - return; } } MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) { MOT::RC res; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetTransactionId(tid); MOT::Table* table = txn->GetTableByExternalId(stmt->relation->foreignOid); @@ -1074,7 +921,18 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) return MOT::RC_ERROR; } - ValidateCreateIndex(stmt, table, txn); + table->GetOrigTable()->WrLock(); + + PG_TRY(); + { + ValidateCreateIndex(stmt, table, txn); + } + PG_CATCH(); + { + table->GetOrigTable()->Unlock(); + PG_RE_THROW(); + } + PG_END_TRY(); elog(LOG, "creating %s index %s (OID: %u), for table: %s", @@ -1093,23 +951,45 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) // check if we have primary and delete previous definition if (stmt->primary) { index_order = MOT::IndexOrder::INDEX_ORDER_PRIMARY; + } else { + if (stmt->unique) { + index_order = MOT::IndexOrder::INDEX_ORDER_SECONDARY_UNIQUE; + } } index = MOT::IndexFactory::CreateIndex(index_order, indexing_method, flavor); if (index == nullptr) { + table->GetOrigTable()->Unlock(); report_pg_error(MOT::RC_ABORT); return MOT::RC_ABORT; } index->SetExtId(stmt->indexOid); - index->SetNumTableFields((uint32_t)table->GetFieldCount()); + if (!index->SetNumTableFields((uint32_t)table->GetFieldCount())) { + table->GetOrigTable()->Unlock(); + delete index; + report_pg_error(MOT::RC_ABORT); + return MOT::RC_ABORT; + } + int count = 0; ListCell* lc = nullptr; foreach (lc, stmt->indexParams) { IndexElem* ielem = (IndexElem*)lfirst(lc); + if (ielem->expr != nullptr) { + table->GetOrigTable()->Unlock(); + delete index; + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmsg("Can't create index on field"), + errdetail("Expressions are not supported"))); + return MOT::RC_ERROR; + } uint64_t colid = table->GetFieldId((ielem->name != nullptr ? ielem->name : ielem->indexcolname)); if (colid == (uint64_t)-1) { // invalid column + table->GetOrigTable()->Unlock(); delete index; ereport(ERROR, (errmodule(MOD_MOT), @@ -1123,6 +1003,7 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) // Temp solution for NULLs, do not allow index creation on column that does not carry not null flag if (!MOT::GetGlobalConfiguration().m_allowIndexOnNullableColumn && !col->m_isNotNull) { + table->GetOrigTable()->Unlock(); delete index; ereport(ERROR, (errmodule(MOD_MOT), @@ -1134,6 +1015,7 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) // Temp solution, we have to support DECIMAL and NUMERIC indexes as well if (col->m_type == MOT::MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL) { + table->GetOrigTable()->Unlock(); delete index; ereport(ERROR, (errmodule(MOD_MOT), @@ -1143,6 +1025,7 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) return MOT::RC_ERROR; } if (col->m_keySize > MAX_KEY_SIZE) { + table->GetOrigTable()->Unlock(); delete index; ereport(ERROR, (errmodule(MOD_MOT), @@ -1160,12 +1043,18 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) index->SetNumIndexFields(count); if ((res = index->IndexInit(keyLength, stmt->unique, stmt->idxname, nullptr)) != MOT::RC_OK) { + table->GetOrigTable()->Unlock(); delete index; report_pg_error(res); return res; } res = txn->CreateIndex(table, index, stmt->primary); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + index->GetExtId(), MOT::DDL_ACCESS_CREATE_INDEX, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + table->GetOrigTable()->Unlock(); if (res != MOT::RC_OK) { delete index; if (res == MOT::RC_TABLE_EXCEEDS_MAX_INDEXES) { @@ -1174,15 +1063,63 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) errcode(ERRCODE_FDW_TOO_MANY_INDEXES), errmsg("Can not create index, max number of indexes %u reached", MAX_NUM_INDEXES))); return MOT::RC_TABLE_EXCEEDS_MAX_INDEXES; - } else { - report_pg_error(txn->m_err, stmt->idxname, txn->m_errMsgBuf); - return MOT::RC_UNIQUE_VIOLATION; } + report_pg_error(txn->m_err, stmt->idxname, txn->m_errMsgBuf); + return MOT::RC_UNIQUE_VIOLATION; } return MOT::RC_OK; } +static void CalculateDecimalColumnTypeLen(MOT::MOT_CATALOG_FIELD_TYPES colType, ColumnDef* colDef, int16& typeLen) +{ + if (colType == MOT::MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL) { + if (list_length(colDef->typname->typmods) > 0) { + bool canMakeShort = true; + int precision = 0; + int scale = 0; + int count = 0; + + ListCell* c = nullptr; + foreach (c, colDef->typname->typmods) { + Node* d = (Node*)lfirst(c); + if (!IsA(d, A_Const)) { + canMakeShort = false; + break; + } + A_Const* ac = (A_Const*)d; + + if (ac->val.type != T_Integer) { + canMakeShort = false; + break; + } + + if (count == 0) { + precision = ac->val.val.ival; + } else { + scale = ac->val.val.ival; + } + + count++; + } + + if (canMakeShort) { + int len = 0; + + len += scale / DEC_DIGITS; + len += (scale % DEC_DIGITS > 0 ? 1 : 0); + + precision -= scale; + + len += precision / DEC_DIGITS; + len += (precision % DEC_DIGITS > 0 ? 1 : 0); + + typeLen = sizeof(MOT::DecimalSt) + len * sizeof(NumericDigit); + } + } + } +} + void MOTAdaptor::AddTableColumns(MOT::Table* table, List* tableElts, bool& hasBlob) { hasBlob = false; @@ -1214,51 +1151,7 @@ void MOTAdaptor::AddTableColumns(MOT::Table* table, List* tableElts, bool& hasBl } hasBlob |= isBlob; - if (colType == MOT::MOT_CATALOG_FIELD_TYPES::MOT_TYPE_DECIMAL) { - if (list_length(colDef->typname->typmods) > 0) { - bool canMakeShort = true; - int precision = 0; - int scale = 0; - int count = 0; - - ListCell* c = nullptr; - foreach (c, colDef->typname->typmods) { - Node* d = (Node*)lfirst(c); - if (!IsA(d, A_Const)) { - canMakeShort = false; - break; - } - A_Const* ac = (A_Const*)d; - - if (ac->val.type != T_Integer) { - canMakeShort = false; - break; - } - - if (count == 0) { - precision = ac->val.val.ival; - } else { - scale = ac->val.val.ival; - } - - count++; - } - - if (canMakeShort) { - int len = 0; - - len += scale / DEC_DIGITS; - len += (scale % DEC_DIGITS > 0 ? 1 : 0); - - precision -= scale; - - len += precision / DEC_DIGITS; - len += (precision % DEC_DIGITS > 0 ? 1 : 0); - - typeLen = sizeof(MOT::DecimalSt) + len * sizeof(NumericDigit); - } - } - } + CalculateDecimalColumnTypeLen(colType, colDef, typeLen); res = table->AddColumn(colDef->colname, typeLen, colType, colDef->is_not_null, typoid); if (res != MOT::RC_OK) { @@ -1274,7 +1167,7 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti { bool hasBlob = false; MOT::Index* primaryIdx = nullptr; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__, tid); MOT::Table* table = nullptr; MOT::RC res = MOT::RC_ERROR; @@ -1305,16 +1198,16 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti errmsg("database with OID %u does not exist", u_sess->proc_cxt.MyDatabaseId))); break; } - tname.append(dbname); - tname.append("_"); + (void)tname.append(dbname); + (void)tname.append("_"); if (stmt->base.relation->schemaname != nullptr) { - tname.append(stmt->base.relation->schemaname); + (void)tname.append(stmt->base.relation->schemaname); } else { - tname.append("#"); + (void)tname.append("#"); } - tname.append("_"); - tname.append(stmt->base.relation->relname); + (void)tname.append("_"); + (void)tname.append(stmt->base.relation->relname); if (!table->Init(stmt->base.relation->relname, tname.c_str(), columnCount, stmt->base.relation->foreignOid)) { delete table; @@ -1362,6 +1255,13 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti break; } + if (!table->InitTombStonePool()) { + delete table; + table = nullptr; + report_pg_error(MOT::RC_MEMORY_ALLOCATION_ERROR); + break; + } + elog(LOG, "creating table %s (OID: %u), num columns: %u, tuple: %u", table->GetLongTableName().c_str(), @@ -1381,12 +1281,19 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti primaryIdx = MOT::IndexFactory::CreatePrimaryIndexEx( MOT::IndexingMethod::INDEXING_METHOD_TREE, DEFAULT_TREE_FLAVOR, 8, table->GetLongTableName(), res, nullptr); if (res != MOT::RC_OK) { - txn->DropTable(table); + (void)txn->DropTable(table); report_pg_error(res); break; } primaryIdx->SetExtId(stmt->base.relation->foreignOid + 1); - primaryIdx->SetNumTableFields(columnCount); + if (!primaryIdx->SetNumTableFields(columnCount)) { + res = MOT::RC_MEMORY_ALLOCATION_ERROR; + (void)txn->DropTable(table); + delete primaryIdx; + report_pg_error(res); + break; + } + primaryIdx->SetNumIndexFields(1); primaryIdx->SetLenghtKeyFields(0, -1, 8); primaryIdx->SetFakePrimary(true); @@ -1397,11 +1304,14 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti if (res != MOT::RC_OK) { if (table != nullptr) { - txn->DropTable(table); + (void)txn->DropTable(table); } if (primaryIdx != nullptr) { delete primaryIdx; } + } else { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + table->GetTableExId(), MOT::DDL_ACCESS_CREATE_TABLE, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); } return res; @@ -1410,7 +1320,7 @@ MOT::RC MOTAdaptor::CreateTable(CreateForeignTableStmt* stmt, ::TransactionId ti MOT::RC MOTAdaptor::DropIndex(DropForeignStmt* stmt, ::TransactionId tid) { MOT::RC res = MOT::RC_OK; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetTransactionId(tid); @@ -1418,8 +1328,18 @@ MOT::RC MOTAdaptor::DropIndex(DropForeignStmt* stmt, ::TransactionId tid) // get table do { - MOT::Index* index = txn->GetIndexByExternalId(stmt->reloid, stmt->indexoid); + MOT::Table* table = txn->GetTableByExternalId(stmt->reloid); + if (table == nullptr) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("Table not found for oid %u", stmt->reloid))); + return MOT::RC_ERROR; + } + table->GetOrigTable()->WrLock(); + MOT::Index* index = table->GetIndexByExtId(stmt->indexoid); if (index == nullptr) { + table->GetOrigTable()->Unlock(); elog(LOG, "Drop index %s error, index oid %u of table oid %u not found.", stmt->name, @@ -1427,14 +1347,15 @@ MOT::RC MOTAdaptor::DropIndex(DropForeignStmt* stmt, ::TransactionId tid) stmt->reloid); res = MOT::RC_INDEX_NOT_FOUND; } else if (index->IsPrimaryKey()) { + table->GetOrigTable()->Unlock(); elog(LOG, "Drop primary index is not supported, failed to drop index: %s", stmt->name); } else { - MOT::Table* table = index->GetTable(); - uint64_t table_relid = table->GetTableExId(); - JitExec::PurgeJitSourceCache(table_relid, false); - table->WrLock(); res = txn->DropIndex(index); - table->Unlock(); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + table->GetTableExId(), MOT::DDL_ACCESS_DROP_INDEX, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + table->GetOrigTable()->Unlock(); } } while (0); @@ -1455,21 +1376,247 @@ MOT::RC MOTAdaptor::DropTable(DropForeignStmt* stmt, ::TransactionId tid) res = MOT::RC_TABLE_NOT_FOUND; elog(LOG, "Drop table %s error, table oid %u not found.", stmt->name, stmt->reloid); } else { - uint64_t table_relid = tab->GetTableExId(); - JitExec::PurgeJitSourceCache(table_relid, false); + tab->GetOrigTable()->WrLock(); res = txn->DropTable(tab); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + tab->GetTableExId(), MOT::DDL_ACCESS_DROP_TABLE, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + tab->GetOrigTable()->Unlock(); } } while (0); return res; } +MOT::RC MOTAdaptor::AlterTableAddColumn(AlterForeingTableCmd* cmd, TransactionId tid) +{ + MOT::RC res = MOT::RC_OK; + MOT::Table* tab = nullptr; + MOT::Column* newColumn = nullptr; + int16 typeLen = 0; + ColumnDef* colDef = (ColumnDef*)cmd->def; + (void)EnsureSafeThreadAccessInline(); + + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + txn->SetTransactionId(tid); + do { + bool isBlob = false; + Oid typoid = InvalidOid; + MOT::MOT_CATALOG_FIELD_TYPES colType; + tab = txn->GetTableByExternalId(cmd->rel->rd_id); + if (tab == nullptr) { + elog(LOG, + "Alter table add column %s error, table oid %u not found.", + NameStr(cmd->rel->rd_rel->relname), + cmd->rel->rd_id); + break; + } + + res = TableFieldType(colDef, colType, &typeLen, typoid, isBlob); + if (res != MOT::RC_OK) { + report_pg_error(res, colDef, (void*)(int64)typeLen); + break; + } + + CalculateDecimalColumnTypeLen(colType, colDef, typeLen); + + // check default value + size_t dftSize = 0; + uintptr_t dftSrc = 0; + Datum dftValue = 0; + bytea* txt = nullptr; + bool shouldFreeTxt = false; + bool isNull = false; + bool hasDefault = false; + char buf[DECIMAL_MAX_SIZE]; + MOT::DecimalSt* d = (MOT::DecimalSt*)buf; + if (cmd->defValue != nullptr) { + if (contain_volatile_functions((Node*)cmd->defValue)) { + ereport(ERROR, + (errcode(ERRCODE_FDW_OPERATION_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("Add column does not support volatile default value"))); + break; + } + + EState* estate = CreateExecutorState(); + ExprState* exprstate = ExecInitExpr(expression_planner(cmd->defValue), NULL); + ExprContext* econtext = GetPerTupleExprContext(estate); + + MemoryContext newcxt = GetPerTupleMemoryContext(estate); + MemoryContext oldcxt = MemoryContextSwitchTo(newcxt); + dftValue = ExecEvalExpr(exprstate, econtext, &isNull, NULL); + (void)MemoryContextSwitchTo(oldcxt); + if (!isNull) { + hasDefault = true; + switch (exprstate->resultType) { + case BYTEAOID: + case TEXTOID: + case VARCHAROID: + case CLOBOID: + case BPCHAROID: { + txt = DatumGetByteaP(dftValue); + dftSize = VARSIZE(txt) - VARHDRSZ; // includes header len VARHDRSZ + dftSrc = (uintptr_t)VARDATA(txt); + shouldFreeTxt = true; + break; + } + case NUMERICOID: { + Numeric n = DatumGetNumeric(dftValue); + if (NUMERIC_NDIGITS(n) > DECIMAL_MAX_DIGITS) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Value exceeds maximum precision: %d", NUMERIC_MAX_PRECISION))); + break; + } + PGNumericToMOT(n, *d); + dftSize = DECIMAL_SIZE(d); + dftSrc = (uintptr_t)d; + break; + } + default: + dftSize = typeLen; + dftSrc = (uintptr_t)dftValue; + break; + } + } + FreeExecutorState(estate); + } + tab->GetOrigTable()->WrLock(); + res = tab->CreateColumn( + newColumn, colDef->colname, typeLen, colType, colDef->is_not_null, typoid, hasDefault, dftSrc, dftSize); + if (res != MOT::RC_OK) { + tab->GetOrigTable()->Unlock(); + report_pg_error(res, colDef, (void*)(int64)typeLen); + break; + } + if (newColumn != nullptr) { + res = txn->AlterTableAddColumn(tab, newColumn); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + tab->GetTableExId(), MOT::DDL_ACCESS_ADD_COLUMN, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + } + tab->GetOrigTable()->Unlock(); + // free if allocated + if (shouldFreeTxt && (char*)dftSrc != (char*)txt) { + pfree(txt); + } + } while (false); + if (res != MOT::RC_OK) { + if (newColumn != nullptr) { + delete newColumn; + } + report_pg_error(res, colDef, (void*)tab); + } + return res; +} + +MOT::RC MOTAdaptor::AlterTableDropColumn(AlterForeingTableCmd* cmd, TransactionId tid) +{ + MOT::RC res = MOT::RC_OK; + (void)EnsureSafeThreadAccessInline(); + + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + txn->SetTransactionId(tid); + do { + MOT::Table* tab = txn->GetTableByExternalId(cmd->rel->rd_id); + if (tab == nullptr) { + elog(LOG, + "Alter table add column %s error, table oid %u not found.", + NameStr(cmd->rel->rd_rel->relname), + cmd->rel->rd_id); + break; + } + tab->GetOrigTable()->WrLock(); + uint64_t colId = tab->GetFieldId(cmd->name); + if (colId == (uint64_t)-1) { + tab->GetOrigTable()->Unlock(); + ereport(ERROR, + (errcode(ERRCODE_FDW_COLUMN_NAME_NOT_FOUND), + errmodule(MOD_MOT), + errmsg("Column %s not found", cmd->name))); + break; + } + MOT::Column* col = tab->GetField(colId); + if (col->IsUsedByIndex()) { + tab->GetOrigTable()->Unlock(); + ereport(ERROR, + (errcode(ERRCODE_FDW_DROP_INDEXED_COLUMN_NOT_ALLOWED), + errmodule(MOD_MOT), + errmsg("Drop column %s is not allowed, used by one or more indexes", cmd->name))); + break; + } + res = txn->AlterTableDropColumn(tab, col); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + tab->GetTableExId(), MOT::DDL_ACCESS_DROP_COLUMN, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + tab->GetOrigTable()->Unlock(); + } while (false); + if (res != MOT::RC_OK) { + report_pg_error(res); + } + return res; +} + +MOT::RC MOTAdaptor::AlterTableRenameColumn(RenameForeingTableCmd* cmd, TransactionId tid) +{ + MOT::RC res = MOT::RC_OK; + (void)EnsureSafeThreadAccessInline(); + + MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); + txn->SetTransactionId(tid); + do { + MOT::Table* tab = txn->GetTableByExternalId(cmd->relid); + if (tab == nullptr) { + elog(LOG, "Alter table rename column error, table oid %u not found.", cmd->relid); + break; + } + tab->GetOrigTable()->WrLock(); + uint64_t colId = tab->GetFieldId(cmd->oldname); + if (colId == (uint64_t)-1) { + tab->GetOrigTable()->Unlock(); + ereport(ERROR, + (errcode(ERRCODE_FDW_COLUMN_NAME_NOT_FOUND), + errmodule(MOD_MOT), + errmsg("Column %s not found", cmd->oldname))); + break; + } + uint16_t len = strlen(cmd->newname); + if (len >= MOT::Column::MAX_COLUMN_NAME_LEN) { + tab->GetOrigTable()->Unlock(); + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmodule(MOD_MOT), + errmsg("Column definition of %s is not supported", cmd->newname), + errdetail("Column name %s exceeds max name size %u", + cmd->newname, + (uint32_t)MOT::Column::MAX_COLUMN_NAME_LEN))); + break; + } + MOT::Column* col = tab->GetField(colId); + res = txn->AlterTableRenameColumn(tab, col, cmd->newname); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + tab->GetTableExId(), MOT::DDL_ACCESS_RENAME_COLUMN, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + tab->GetOrigTable()->Unlock(); + } while (false); + if (res != MOT::RC_OK) { + report_pg_error(res); + } + return res; +} + MOT::RC MOTAdaptor::TruncateTable(Relation rel, ::TransactionId tid) { MOT::RC res = MOT::RC_OK; MOT::Table* tab = nullptr; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetTransactionId(tid); @@ -1482,26 +1629,28 @@ MOT::RC MOTAdaptor::TruncateTable(Relation rel, ::TransactionId tid) break; } - JitExec::PurgeJitSourceCache(rel->rd_id, true); - tab->WrLock(); + tab->GetOrigTable()->WrLock(); res = txn->TruncateTable(tab); - tab->Unlock(); + if (res == MOT::RC_OK) { + MOT::MOTEngine::GetInstance()->NotifyDDLEvent( + tab->GetTableExId(), MOT::DDL_ACCESS_TRUNCATE_TABLE, MOT::TxnDDLPhase::TXN_DDL_PHASE_EXEC); + } + tab->GetOrigTable()->Unlock(); } while (0); return res; } -MOT::RC MOTAdaptor::VacuumTable(Relation rel, ::TransactionId tid) +void MOTAdaptor::VacuumTable(Relation rel, ::TransactionId tid) { - MOT::RC res = MOT::RC_OK; MOT::Table* tab = nullptr; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); txn->SetTransactionId(tid); elog(LOG, "vacuuming table %s, oid: %u", NameStr(rel->rd_rel->relname), rel->rd_id); do { - tab = MOT::GetTableManager()->GetTableSafeByExId(rel->rd_id); + tab = MOT::GetTableManager()->GetTableSafeByExId(rel->rd_id, true); if (tab == nullptr) { elog(LOG, "Vacuum table %s error, table oid %u not found.", NameStr(rel->rd_rel->relname), rel->rd_id); break; @@ -1510,16 +1659,16 @@ MOT::RC MOTAdaptor::VacuumTable(Relation rel, ::TransactionId tid) tab->Compact(txn); tab->Unlock(); } while (0); - return res; } uint64_t MOTAdaptor::GetTableIndexSize(uint64_t tabId, uint64_t ixId) { uint64_t res = 0; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MOT::TxnManager* txn = GetSafeTxn(__FUNCTION__); MOT::Table* tab = nullptr; MOT::Index* ix = nullptr; + uint64_t netTotal = 0; do { tab = txn->GetTableByExternalId(tabId); @@ -1540,9 +1689,10 @@ uint64_t MOTAdaptor::GetTableIndexSize(uint64_t tabId, uint64_t ixId) errmsg("Get index size error, index oid %lu for table oid %lu not found.", ixId, tabId))); break; } - res = ix->GetIndexSize(); - } else - res = tab->GetTableSize(); + res = ix->GetIndexSize(netTotal); + } else { + res = tab->GetTableSize(netTotal); + } } while (0); return res; @@ -1550,21 +1700,15 @@ uint64_t MOTAdaptor::GetTableIndexSize(uint64_t tabId, uint64_t ixId) MotMemoryDetail* MOTAdaptor::GetMemSize(uint32_t* nodeCount, bool isGlobal) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MotMemoryDetail* result = nullptr; *nodeCount = 0; /* We allocate an array of size (m_nodeCount + 1) to accommodate one aggregated entry of all global pools. */ uint32_t statsArraySize = MOT::g_memGlobalCfg.m_nodeCount + 1; MOT::MemRawChunkPoolStats* chunkPoolStatsArray = - (MOT::MemRawChunkPoolStats*)palloc(statsArraySize * sizeof(MOT::MemRawChunkPoolStats)); + (MOT::MemRawChunkPoolStats*)palloc0(statsArraySize * sizeof(MOT::MemRawChunkPoolStats)); if (chunkPoolStatsArray != nullptr) { - errno_t erc = memset_s(chunkPoolStatsArray, - statsArraySize * sizeof(MOT::MemRawChunkPoolStats), - 0, - statsArraySize * sizeof(MOT::MemRawChunkPoolStats)); - securec_check(erc, "\0", "\0"); - uint32_t realStatsEntries; if (isGlobal) { realStatsEntries = MOT::MemRawChunkStoreGetGlobalStats(chunkPoolStatsArray, statsArraySize); @@ -1574,7 +1718,7 @@ MotMemoryDetail* MOTAdaptor::GetMemSize(uint32_t* nodeCount, bool isGlobal) MOT_ASSERT(realStatsEntries <= statsArraySize); if (realStatsEntries > 0) { - result = (MotMemoryDetail*)palloc(realStatsEntries * sizeof(MotMemoryDetail)); + result = (MotMemoryDetail*)palloc0(realStatsEntries * sizeof(MotMemoryDetail)); if (result != nullptr) { for (uint32_t node = 0; node < realStatsEntries; ++node) { result[node].numaNode = chunkPoolStatsArray[node].m_node; @@ -1592,17 +1736,17 @@ MotMemoryDetail* MOTAdaptor::GetMemSize(uint32_t* nodeCount, bool isGlobal) MotSessionMemoryDetail* MOTAdaptor::GetSessionMemSize(uint32_t* sessionCount) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); MotSessionMemoryDetail* result = nullptr; *sessionCount = 0; uint32_t session_count = MOT::g_memGlobalCfg.m_maxThreadCount; MOT::MemSessionAllocatorStats* session_stats_array = - (MOT::MemSessionAllocatorStats*)palloc(session_count * sizeof(MOT::MemSessionAllocatorStats)); + (MOT::MemSessionAllocatorStats*)palloc0(session_count * sizeof(MOT::MemSessionAllocatorStats)); if (session_stats_array != nullptr) { uint32_t real_session_count = MOT::MemSessionGetAllStats(session_stats_array, session_count); if (real_session_count > 0) { - result = (MotSessionMemoryDetail*)palloc(real_session_count * sizeof(MotSessionMemoryDetail)); + result = (MotSessionMemoryDetail*)palloc0(real_session_count * sizeof(MotSessionMemoryDetail)); if (result != nullptr) { for (uint32_t session_index = 0; session_index < real_session_count; ++session_index) { GetSessionDetails(session_stats_array[session_index].m_sessionId, @@ -1625,7 +1769,7 @@ void MOTAdaptor::CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start { uint8_t* buf = nullptr; uint8_t pattern = 0x00; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); int16_t num = festate->m_bestIx->m_ix->GetNumFields(); const uint16_t* fieldLengths = festate->m_bestIx->m_ix->GetLengthKeyFields(); const int16_t* orgCols = festate->m_bestIx->m_ix->GetColumnKeyFields(); @@ -1671,7 +1815,7 @@ void MOTAdaptor::CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start break; default: - elog(LOG, "Invalid key operation: %u", oper); + elog(LOG, "Invalid key operation: %" PRIu8, static_cast(oper)); break; } @@ -1709,7 +1853,7 @@ void MOTAdaptor::CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start case KEY_OPER::READ_KEY_EXACT: default: - elog(LOG, "Invalid key operation: %u", oper); + elog(LOG, "Invalid key operation: %" PRIu8, static_cast(oper)); break; } } @@ -1725,7 +1869,7 @@ void MOTAdaptor::CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start } } else { MOT_ASSERT((offset + fieldLengths[i]) <= keyLength); - festate->m_stateKey[start].FillPattern(pattern, fieldLengths[i], offset); + (void)festate->m_stateKey[start].FillPattern(pattern, fieldLengths[i], offset); } offset += fieldLengths[i]; @@ -1737,7 +1881,7 @@ void MOTAdaptor::CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start bool MOTAdaptor::IsScanEnd(MOTFdwStateSt* festate) { bool res = false; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); // festate->cursor[1] (end iterator) might be NULL (in case it is not in use). If this is the case, return false // (which means we have not reached the end yet) @@ -1773,7 +1917,7 @@ bool MOTAdaptor::IsScanEnd(MOTFdwStateSt* festate) void MOTAdaptor::PackRow(TupleTableSlot* slot, MOT::Table* table, uint8_t* attrs_used, uint8_t* destRow) { errno_t erc; - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); HeapTuple srcData = (HeapTuple)slot->tts_tuple; TupleDesc tupdesc = slot->tts_tupleDescriptor; bool hasnulls = HeapTupleHasNulls(srcData); @@ -1806,7 +1950,7 @@ void MOTAdaptor::PackRow(TupleTableSlot* slot, MOT::Table* table, uint8_t* attrs void MOTAdaptor::PackUpdateRow(TupleTableSlot* slot, MOT::Table* table, const uint8_t* attrs_used, uint8_t* destRow) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); TupleDesc tupdesc = slot->tts_tupleDescriptor; uint8_t* bits; uint64_t i = 0; @@ -1833,7 +1977,7 @@ void MOTAdaptor::PackUpdateRow(TupleTableSlot* slot, MOT::Table* table, const ui void MOTAdaptor::UnpackRow(TupleTableSlot* slot, MOT::Table* table, const uint8_t* attrs_used, uint8_t* srcRow) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); TupleDesc tupdesc = slot->tts_tupleDescriptor; uint64_t i = 0; @@ -1841,9 +1985,9 @@ void MOTAdaptor::UnpackRow(TupleTableSlot* slot, MOT::Table* table, const uint8_ uint64_t cols = table->GetFieldCount() - 1; for (; i < cols; i++) { - if (BITMAP_GET(attrs_used, i)) + if (BITMAP_GET(attrs_used, i)) { MOTToDatum(table, tupdesc->attrs[i], srcRow, &(slot->tts_values[i]), &(slot->tts_isnull[i])); - else { + } else { slot->tts_isnull[i] = true; slot->tts_values[i] = PointerGetDatum(nullptr); } @@ -1853,7 +1997,7 @@ void MOTAdaptor::UnpackRow(TupleTableSlot* slot, MOT::Table* table, const uint8_ // useful functions for data conversion: utils/fmgr/gmgr.cpp void MOTAdaptor::MOTToDatum(MOT::Table* table, const Form_pg_attribute attr, uint8_t* data, Datum* value, bool* is_null) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); if (!BITMAP_GET(data, (attr->attnum - 1))) { *is_null = true; *value = PointerGetDatum(nullptr); @@ -1875,8 +2019,10 @@ void MOTAdaptor::MOTToDatum(MOT::Table* table, const Form_pg_attribute attr, uin col->Unpack(data, &tmp, len); bytea* result = (bytea*)palloc(len + VARHDRSZ); - errno_t erc = memcpy_s(VARDATA(result), len, (uint8_t*)tmp, len); - securec_check(erc, "\0", "\0"); + if (len > 0) { + errno_t erc = memcpy_s(VARDATA(result), len, (uint8_t*)tmp, len); + securec_check(erc, "\0", "\0"); + } SET_VARSIZE(result, len + VARHDRSZ); *value = PointerGetDatum(result); @@ -1897,7 +2043,7 @@ void MOTAdaptor::MOTToDatum(MOT::Table* table, const Form_pg_attribute attr, uin void MOTAdaptor::DatumToMOT(MOT::Column* col, Datum datum, Oid type, uint8_t* data) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); switch (type) { case BYTEAOID: case TEXTOID: @@ -1907,7 +2053,12 @@ void MOTAdaptor::DatumToMOT(MOT::Column* col, Datum datum, Oid type, uint8_t* da bytea* txt = DatumGetByteaP(datum); size_t size = VARSIZE(txt); // includes header len VARHDRSZ char* src = VARDATA(txt); - col->Pack(data, (uintptr_t)src, size - VARHDRSZ); + if (!col->Pack(data, (uintptr_t)src, size - VARHDRSZ)) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("value too long for column size (%d)", (int)(col->m_size - VARHDRSZ)))); + } if ((char*)datum != (char*)txt) { pfree(txt); @@ -1928,17 +2079,16 @@ void MOTAdaptor::DatumToMOT(MOT::Column* col, Datum datum, Oid type, uint8_t* da break; } PGNumericToMOT(n, *d); - col->Pack(data, (uintptr_t)d, DECIMAL_SIZE(d)); - + (void)col->Pack(data, (uintptr_t)d, DECIMAL_SIZE(d)); // simple types packing cannot fail break; } default: - col->Pack(data, datum, col->m_size); + (void)col->Pack(data, datum, col->m_size); // no verification required break; } } -inline void MOTAdaptor::VarcharToMOTKey( +void MOTAdaptor::VarcharToMOTKey( MOT::Column* col, Oid datumType, Datum datum, Oid colType, uint8_t* data, size_t len, KEY_OPER oper, uint8_t fill) { bool noValue = false; @@ -1963,11 +2113,6 @@ inline void MOTAdaptor::VarcharToMOTKey( bytea* txt = DatumGetByteaP(datum); size_t size = VARSIZE(txt); // includes header len VARHDRSZ char* src = VARDATA(txt); - - if (size > len) { - size = len; - } - size -= VARHDRSZ; if (oper == KEY_OPER::READ_KEY_LIKE) { if (src[size - 1] == '%') { @@ -1983,14 +2128,20 @@ inline void MOTAdaptor::VarcharToMOTKey( } else if (colType == BPCHAROID) { // handle padding for blank-padded type fill = 0x20; } - col->PackKey(data, (uintptr_t)src, size, fill); + // if the length of search value is bigger than a column key size (len) + // we make key only from a part that equals column key length + // this will override the column delimiter exactly by one char from search value + if (size > len) { + size = len; + } + (void)col->PackKey(data, (uintptr_t)src, size, fill); if ((char*)datum != (char*)txt) { pfree(txt); } } -inline void MOTAdaptor::FloatToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) +void MOTAdaptor::FloatToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) { if (datumType == FLOAT8OID) { MOT::DoubleConvT dc; @@ -1998,64 +2149,64 @@ inline void MOTAdaptor::FloatToMOTKey(MOT::Column* col, Oid datumType, Datum dat dc.m_r = (uint64_t)datum; fc.m_v = (float)dc.m_v; uint64_t u = (uint64_t)fc.m_r; - col->PackKey(data, u, col->m_size); + (void)col->PackKey(data, u, col->m_size); } else { - col->PackKey(data, datum, col->m_size); + (void)col->PackKey(data, datum, col->m_size); } } -inline void MOTAdaptor::NumericToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) +void MOTAdaptor::NumericToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) { Numeric n = DatumGetNumeric(datum); char buf[DECIMAL_MAX_SIZE]; MOT::DecimalSt* d = (MOT::DecimalSt*)buf; PGNumericToMOT(n, *d); - col->PackKey(data, (uintptr_t)d, DECIMAL_SIZE(d)); + (void)col->PackKey(data, (uintptr_t)d, DECIMAL_SIZE(d)); } -inline void MOTAdaptor::TimestampToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) +void MOTAdaptor::TimestampToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) { if (datumType == TIMESTAMPTZOID) { Timestamp result = DatumGetTimestamp(DirectFunctionCall1(timestamptz_timestamp, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else if (datumType == DATEOID) { Timestamp result = DatumGetTimestamp(DirectFunctionCall1(date_timestamp, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else { - col->PackKey(data, datum, col->m_size); + (void)col->PackKey(data, datum, col->m_size); } } -inline void MOTAdaptor::TimestampTzToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) +void MOTAdaptor::TimestampTzToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) { if (datumType == TIMESTAMPOID) { TimestampTz result = DatumGetTimestampTz(DirectFunctionCall1(timestamp_timestamptz, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else if (datumType == DATEOID) { TimestampTz result = DatumGetTimestampTz(DirectFunctionCall1(date_timestamptz, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else { - col->PackKey(data, datum, col->m_size); + (void)col->PackKey(data, datum, col->m_size); } } -inline void MOTAdaptor::DateToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) +void MOTAdaptor::DateToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data) { if (datumType == TIMESTAMPOID) { DateADT result = DatumGetDateADT(DirectFunctionCall1(timestamp_date, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else if (datumType == TIMESTAMPTZOID) { DateADT result = DatumGetDateADT(DirectFunctionCall1(timestamptz_date, datum)); - col->PackKey(data, result, col->m_size); + (void)col->PackKey(data, result, col->m_size); } else { - col->PackKey(data, datum, col->m_size); + (void)col->PackKey(data, datum, col->m_size); } } void MOTAdaptor::DatumToMOTKey( MOT::Column* col, Oid datumType, Datum datum, Oid colType, uint8_t* data, size_t len, KEY_OPER oper, uint8_t fill) { - EnsureSafeThreadAccessInline(); + (void)EnsureSafeThreadAccessInline(); switch (colType) { case BYTEAOID: case TEXTOID: @@ -2080,116 +2231,7 @@ void MOTAdaptor::DatumToMOTKey( DateToMOTKey(col, datumType, datum, data); break; default: - col->PackKey(data, datum, col->m_size); + (void)col->PackKey(data, datum, col->m_size); break; } } - -MOTFdwStateSt* InitializeFdwState(void* fdwState, List** fdwExpr, uint64_t exTableID) -{ - MOTFdwStateSt* state = (MOTFdwStateSt*)palloc0(sizeof(MOTFdwStateSt)); - List* values = (List*)fdwState; - - state->m_allocInScan = true; - state->m_foreignTableId = exTableID; - if (list_length(values) > 0) { - ListCell* cell = list_head(values); - int type = ((Const*)lfirst(cell))->constvalue; - if (type != FDW_LIST_STATE) { - return state; - } - cell = lnext(cell); - state->m_cmdOper = (CmdType)((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_order = (SORTDIR_ENUM)((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_hasForUpdate = (bool)((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_foreignTableId = ((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_numAttrs = ((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_ctidNum = ((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - state->m_numExpr = ((Const*)lfirst(cell))->constvalue; - cell = lnext(cell); - - int len = BITMAP_GETLEN(state->m_numAttrs); - state->m_attrsUsed = (uint8_t*)palloc0(len); - state->m_attrsModified = (uint8_t*)palloc0(len); - BitmapDeSerialize(state->m_attrsUsed, len, &cell); - - if (cell != NULL) { - state->m_bestIx = &state->m_bestIxBuf; - state->m_bestIx->Deserialize(cell, exTableID); - } - - if (fdwExpr != NULL && *fdwExpr != NULL) { - ListCell* c = NULL; - int i = 0; - - // divide fdw expr to param list and original expr - state->m_remoteCondsOrig = NULL; - - foreach (c, *fdwExpr) { - if (i < state->m_numExpr) { - i++; - continue; - } else { - state->m_remoteCondsOrig = lappend(state->m_remoteCondsOrig, lfirst(c)); - } - } - - *fdwExpr = list_truncate(*fdwExpr, state->m_numExpr); - } - } - return state; -} - -void* SerializeFdwState(MOTFdwStateSt* state) -{ - List* result = NULL; - - // set list type to FDW_LIST_STATE - result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, FDW_LIST_STATE, false, true)); - result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_cmdOper), false, true)); - result = lappend(result, makeConst(INT1OID, -1, InvalidOid, 4, Int8GetDatum(state->m_order), false, true)); - result = lappend(result, makeConst(BOOLOID, -1, InvalidOid, 1, BoolGetDatum(state->m_hasForUpdate), false, true)); - result = - lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_foreignTableId), false, true)); - result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_numAttrs), false, true)); - result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(state->m_ctidNum), false, true)); - result = lappend(result, makeConst(INT2OID, -1, InvalidOid, 2, Int16GetDatum(state->m_numExpr), false, true)); - int len = BITMAP_GETLEN(state->m_numAttrs); - result = BitmapSerialize(result, state->m_attrsUsed, len); - - if (state->m_bestIx != nullptr) { - state->m_bestIx->Serialize(&result); - } - ReleaseFdwState(state); - return result; -} - -void ReleaseFdwState(MOTFdwStateSt* state) -{ - CleanCursors(state); - - if (state->m_currTxn) { - state->m_currTxn->m_queryState.erase((uint64_t)state); - } - - if (state->m_bestIx && state->m_bestIx != &state->m_bestIxBuf) - pfree(state->m_bestIx); - - if (state->m_remoteCondsOrig != nullptr) - list_free(state->m_remoteCondsOrig); - - if (state->m_attrsUsed != NULL) - pfree(state->m_attrsUsed); - - if (state->m_attrsModified != NULL) - pfree(state->m_attrsModified); - - state->m_table = NULL; - pfree(state); -} diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.h b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.h index 7f1c1e485..91ff971cb 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.h +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.h @@ -37,6 +37,7 @@ #include "global.h" #include "mot_fdw_error.h" #include "mot_fdw_xlog.h" +#include "mot_fdw_snapshot_manager.h" #include "mot_engine.h" #include "bitmapset.h" #include "storage/mot/jit_exec.h" @@ -45,7 +46,7 @@ using std::map; using std::string; -#define MIN_DYNAMIC_PROCESS_MEMORY 2 * 1024 * 1024 +#define MIN_DYNAMIC_PROCESS_MEMORY (2 * 1024 * 1024) #define MOT_INSERT_FAILED_MSG "Insert failed" #define MOT_UPDATE_FAILED_MSG "Update failed" @@ -67,6 +68,17 @@ using std::string; #define abortParentTransactionParamsNoDetail(error, msg, ...) \ ereport(ERROR, (errmodule(MOD_MOT), errcode(error), errmsg(msg, __VA_ARGS__))); +#define raiseAbortTxnError() \ + { \ + ereport(ERROR, \ + (errmodule(MOD_MOT), \ + errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), \ + errmsg("current transaction is aborted, " \ + "commands ignored until end of transaction block, firstChar[%c]", \ + u_sess->proc_cxt.firstChar), \ + errdetail("Please perform rollback"))); \ + } + #define isMemoryLimitReached() \ { \ if (MOTAdaptor::m_engine->IsSoftMemoryLimitReached()) { \ @@ -82,6 +94,13 @@ using std::string; } \ } +#define IS_CHAR_TYPE(oid) \ + ((oid) == VARCHAROID || (oid) == BPCHAROID || (oid) == TEXTOID || (oid) == CLOBOID || (oid) == BYTEAOID) +#define IS_INT_TYPE(oid) \ + ((oid) == BOOLOID || (oid) == CHAROID || (oid) == INT8OID || (oid) == INT2OID || (oid) == INT4OID || \ + (oid) == FLOAT4OID || (oid) == FLOAT8OID || (oid) == INT1OID) +#define IS_TIME_TYPE(oid) ((oid) == DATEOID || (oid) == TIMEOID || (oid) == TIMESTAMPOID || (oid) == TIMESTAMPTZOID) + namespace MOT { class Table; class Index; @@ -90,21 +109,16 @@ class Column; class MOTEngine; } // namespace MOT -#ifndef MOTFdwStateSt -typedef struct MOTFdwState_St MOTFdwStateSt; -#endif - -typedef enum : uint8_t { SORTDIR_NONE = 0, SORTDIR_ASC = 1, SORTDIR_DESC = 2 } SORTDIR_ENUM; -typedef enum : uint8_t { FDW_LIST_STATE = 1, FDW_LIST_BITMAP = 2 } FDW_LIST_TYPE; +enum class FDW_LIST_TYPE : uint8_t { FDW_LIST_STATE = 1, FDW_LIST_BITMAP = 2 }; typedef struct Order_St { - SORTDIR_ENUM m_order; + SortDir m_order; int m_lastMatch; int m_cols[MAX_KEY_COLUMNS]; void init() { - m_order = SORTDIR_NONE; + m_order = SortDir::SORTDIR_NONE; m_lastMatch = -1; for (uint32_t i = 0; i < MAX_KEY_COLUMNS; i++) m_cols[i] = 0; @@ -113,6 +127,10 @@ typedef struct Order_St { #define MOT_REC_TID_NAME "ctid" +/** + * @struct MOTRecConvert + * @brief Union to impersonate PG CTID field + */ typedef struct MOTRecConvert { union { uint64_t m_ptr; @@ -120,50 +138,134 @@ typedef struct MOTRecConvert { } m_u; } MOTRecConvertSt; -#define SORT_STRATEGY(x) ((x == BTGreaterStrategyNumber) ? SORTDIR_DESC : SORTDIR_ASC) +#define SORT_STRATEGY(x) (((x) == BTGreaterStrategyNumber) ? SortDir::SORTDIR_DESC : SortDir::SORTDIR_ASC) +/** + * @struct MOTFdwState_St + * @brief Structure used to pass information between planning and execution phase + * of the query. Also, used for translation between PG and MOT data representation + */ struct MOTFdwState_St { + /** @var indicates PG transaction id */ ::TransactionId m_txnId; + + /** @var indicates if the instance was initiated during SCAN operation */ bool m_allocInScan; + + /** @var Query operation SELECT/INSERT... */ CmdType m_cmdOper; - SORTDIR_ENUM m_order; + + /** @var Sorting direction ASC/DESC */ + SortDir m_order; + + /** @var Select query uses FOR UPDATE syntax */ bool m_hasForUpdate; + + /** @var Indicates if during update indexed column is updated */ + MOT::UpdateIndexColumnType m_hasIndexedColUpdate; + + /** @var PG global id for table */ Oid m_foreignTableId; + + /** @var number of columns in the table */ AttrNumber m_numAttrs; + + /** @var CTID column index in the resulting column array */ AttrNumber m_ctidNum; + + /** @var number of conditions used FDW engine */ uint16_t m_numExpr; + + /** @var bitmap which contains column indexes used in the query */ uint8_t* m_attrsUsed; - uint8_t* m_attrsModified; // this will be merged into attrs_used in BeginModify + + /** + * @var m_attrsModified which contains column indexes used in the update. + * This will be merged into m_attrsUsed in BeginModify + */ + uint8_t* m_attrsModified; + + /** @var expression values*/ List* m_remoteConds; + + /** @var original expressions of a form "a < b" */ List* m_remoteCondsOrig; + + /** @var expressions to be passed to a PG filter */ List* m_localConds; + + /** @var Initialized expressions for creating key buffers */ List* m_execExprs; + + /** @var context for extracting expression value */ ExprContext* m_econtext; + + /** @var initial full scan cost of the table */ double m_startupCost; + + /** @var scan cost after using query conditions */ double m_totalCost; + /** @var best index to perform query based on const conditions only */ MatchIndex* m_bestIx; + + /** @var best index to perform query based on parametrized conditions */ MatchIndex* m_paramBestIx; + + /** @var placeholder for above members */ MatchIndex m_bestIxBuf; // ENGINE + /** @var pointer to a MOT table structure during execution */ MOT::Table* m_table; + + /** @var start and end cursors for scan operations */ MOT::IndexIterator* m_cursor[2] = {nullptr, nullptr}; + + /** @var current transaction manager */ MOT::TxnManager* m_currTxn; - void* m_currItem = nullptr; + + /** @var number of rows fetched by scan */ uint32_t m_rowsFound = 0; + + /** @var indicates if cursors were initialized */ bool m_cursorOpened = false; + + /** @var key buffers for cursors initialization */ MOT::MaxKey m_stateKey[2]; + + /** @var indicates direction of scan */ bool m_forwardDirectionScan; + + /** @var MOT internal command */ MOT::AccessType m_internalCmdOper; }; +/** + * @class MOTAdaptor + * @brief Interface for PG and MOT communication + */ class MOTAdaptor { public: + /** + * @brief Initialize MOT engine singleton. + */ static void Init(); + + /** + * @brief Destroy MOT engine singleton. + */ static void Destroy(); + + /** + * @brief Indicates that MOT configuration file has been changed. + */ static void NotifyConfigChange(); + /** + * @brief Set internal MOT query command. + * @param festate MOT Query execution state. + */ static inline void GetCmdOper(MOTFdwStateSt* festate) { switch (festate->m_cmdOper) { @@ -193,33 +295,179 @@ public: } } + /** + * @brief Initialize MOT transaction manager and session. + * @param callerSrc Name of the function initiated the call + * @param connection_id MOT connection id + */ static MOT::TxnManager* InitTxnManager( const char* callerSrc, MOT::ConnectionId connection_id = INVALID_CONNECTION_ID); - static void DestroyTxn(int status, Datum ptr); - static void DeleteTablePtr(MOT::Table* t); + /** + * @brief Destroy MOT transaction manager and session. + * @param status PG connection status + * @param ptr Pointer to MOT internal data (session) + */ + static void DestroyTxn(int status, Datum ptr); + + /** + * @brief Create MOT table from PG create table statement. + * @param table PG table statement + * @param tid transaction id + */ static MOT::RC CreateTable(CreateForeignTableStmt* table, TransactionId tid); + + /** + * @brief Create MOT index from PG create index statement. + * @param index PG index statement + * @param tid transaction id + */ static MOT::RC CreateIndex(IndexStmt* index, TransactionId tid); + + /** + * @brief Drop MOT index from PG drop index statement. + * @param stmt PG drop index statement + * @param tid transaction id + */ static MOT::RC DropIndex(DropForeignStmt* stmt, TransactionId tid); + + /** + * @brief Drop MOT table from PG drop table statement. + * @param stmt PG drop table statement + * @param tid transaction id + */ static MOT::RC DropTable(DropForeignStmt* stmt, TransactionId tid); + + /** + * @brief Add MOT column from PG add column statement. + * @param cmd PG add column statement + * @param tid transaction id + */ + static MOT::RC AlterTableAddColumn(AlterForeingTableCmd* cmd, TransactionId tid); + + /** + * @brief Drop MOT column from PG drop column statement. + * @param cmd PG drop column statement + * @param tid transaction id + */ + static MOT::RC AlterTableDropColumn(AlterForeingTableCmd* cmd, TransactionId tid); + + /** + * @brief Rename MOT column from PG rename column statement. + * @param cmd PG rename column statement + * @param tid transaction id + */ + static MOT::RC AlterTableRenameColumn(RenameForeingTableCmd* cmd, TransactionId tid); + + /** + * @brief Truncate MOT table. + * @param rel PG table structure + * @param tid transaction id + */ static MOT::RC TruncateTable(Relation rel, TransactionId tid); - static MOT::RC VacuumTable(Relation rel, TransactionId tid); + + /** + * @brief Vacuum MOT table. + * @param rel PG table structure + * @param tid transaction id + */ + static void VacuumTable(Relation rel, TransactionId tid); + + /** + * @brief Returns index memory size. + * @param tabId PG OID for table + * @param ixId PG OID for index + */ static uint64_t GetTableIndexSize(uint64_t tabId, uint64_t ixId); + + /** + * @brief Returns MOT memory size. + * @param nodeCount OUT holds number of numa sockets + * @param isGlobal indicator to count global or local (session) memory + */ static MotMemoryDetail* GetMemSize(uint32_t* nodeCount, bool isGlobal); + + /** + * @brief Returns session memory stats. + * @param sessionCount OUT holds number of sessions + */ static MotSessionMemoryDetail* GetSessionMemSize(uint32_t* sessionCount); + + /** + * @brief Validates transaction before actual commit. + */ static MOT::RC ValidateCommit(); + + /** + * @brief Performs actual commit. + * @param csn transaction id + */ static void RecordCommit(uint64_t csn); - static MOT::RC Commit(uint64_t csn); // Does both ValidateCommit and RecordCommit + + /** + * @brief Performs validate and actual commit. + * @param csn transaction id + */ + static MOT::RC Commit(uint64_t csn); + + /** + * @brief Returns true in case transaction is read only. + */ + static bool IsTxnWriteSetEmpty(); + + /** + * @brief Cleanup transaction manager after transaction ends. + */ static void EndTransaction(); + + /** + * @brief Performs rollback of transaction. + */ static void Rollback(); + + /** + * @brief Performs prepare of transaction in case of two-phase commit. + */ static MOT::RC Prepare(); + + /** + * @brief Performs actual commit of prepared transaction. + * @param csn transaction id + */ static void CommitPrepared(uint64_t csn); + + /** + * @brief Performs rollback of prepared transaction. + */ static void RollbackPrepared(); + + /** + * @brief Performs insert into MOT engine. + * @param fdwState query state + * @param slot PG row + */ static MOT::RC InsertRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot); + + /** + * @brief Performs update of a fetched row. + * @param fdwState query state + * @param slot PG row containing changes + * @param currRow MOT row to be updated + */ static MOT::RC UpdateRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot, MOT::Row* currRow); + + /** + * @brief Performs delete of a row. + * @param fdwState query state + * @param slot PG row to be deleted + */ static MOT::RC DeleteRow(MOTFdwStateSt* fdwState, TupleTableSlot* slot); - /* Convertors */ + /** + * @brief Performs conversion of DECIMAL PG type to MOT type. + * @param n PG value + * @param[out] d MOT value + */ inline static void PGNumericToMOT(const Numeric n, MOT::DecimalSt& d) { int sign = NUMERIC_SIGN(n); @@ -241,6 +489,10 @@ public: } } + /** + * @brief Performs conversion of DECIMAL MOT type to PG type. + * @param d MOT value + */ inline static Numeric MOTNumericToPG(MOT::DecimalSt* d) { NumericVar v; @@ -258,26 +510,121 @@ public: return makeNumeric(&v); } - // data conversion + /** + * @brief Performs conversion of value from PG type to MOT type. + * @param col MOT column + * @param datum PG value + * @parma type PG type id + * @parma data MOT data pointer + */ static void DatumToMOT(MOT::Column* col, Datum datum, Oid type, uint8_t* data); + + /** + * @brief Performs conversion of value from PG type to MOT key type. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param colType PG column type + * @param data MOT key placeholder + * @param len MOT placeholder length + * @param oper MOT key operation type + * @param fill MOT fill value + */ static void DatumToMOTKey(MOT::Column* col, Oid datumType, Datum datum, Oid colType, uint8_t* data, size_t len, KEY_OPER oper, uint8_t fill = 0x00); + + /** + * @brief Performs conversion of value from MOT to PG. + * @param table MOT table + * @param attr PG column metadata + * @param data MOT pointer to a row data + * @param value PG placeholder for a value + * @param is_null indicates if requested column value is null + */ static void MOTToDatum(MOT::Table* table, const Form_pg_attribute attr, uint8_t* data, Datum* value, bool* is_null); + /** + * @brief Performs conversion of PG row to MOT row. + * @param slot PG row + * @param table MOT table + * @param attrs_used bitmap indicating which columns should be converted + * @parma destRow MOT data holder for a row + */ static void PackRow(TupleTableSlot* slot, MOT::Table* table, uint8_t* attrs_used, uint8_t* destRow); + + /** + * @brief Performs conversion of PG row to MOT row. + * @param slot PG row + * @param table MOT table + * @param attrs_used bitmap indicating which columns should be converted + * @parma destRow MOT data holder for a row + */ static void PackUpdateRow(TupleTableSlot* slot, MOT::Table* table, const uint8_t* attrs_used, uint8_t* destRow); + + /** + * @brief Performs conversion of MOT row to PG row. + * @param slot PG row + * @param table MOT table + * @param attrs_used bitmap indicating which columns should be converted + * @parma srcRow MOT data holder for a row + */ static void UnpackRow(TupleTableSlot* slot, MOT::Table* table, const uint8_t* attrs_used, uint8_t* srcRow); - // scan helpers + /** + * @brief Performs open of scan cursors for a query. + * @param rel PG table + * @param festate MOT query state + */ static void OpenCursor(Relation rel, MOTFdwStateSt* festate); + + /** + * @brief Verifies if scan has reached the end. + * @param festate MOT query state + */ static bool IsScanEnd(MOTFdwStateSt* festate); + + /** + * @brief Creates key buffer for scan operation. + * @param rel PG table + * @param festate MOT query state + * @param start indicates for which cursor key buffer is created (start,end) + */ static void CreateKeyBuffer(Relation rel, MOTFdwStateSt* festate, int start); - // planning helpers + /** + * @brief Returns true in case column participates in index. + * @param colId column index in metadata + * @param table MOT table + */ + static bool IsColumnIndexed(int16_t colId, MOT::Table* table); + + /** + * @brief Associates query expression with index column. + * @param state query state + * @param marr array of matched indexes for query execution + * @param colId column index in the table + * @param expr PG expression containing value + * @param parent parent expression for a value + * @param set_local indicates if the expression should be processed by PG + */ static bool SetMatchingExpr(MOTFdwStateSt* state, MatchIndexArr* marr, int16_t colId, KEY_OPER op, Expr* expr, Expr* parent, bool set_local); + + /** + * @brief Peeks the best index for scan operation among matched indexes. + * @param festate query state + * @param marr array of matched indexes for query execution + * @param numClauses number of conditions used in query + * @param setLocal indicates if the expression should be processed by PG + */ static MatchIndex* GetBestMatchIndex( MOTFdwStateSt* festate, MatchIndexArr* marr, int numClauses, bool setLocal = true); + + /** + * @brief Add parameter expression to the list. + * @param params list of expressions + * @param expr expression to be added + */ inline static int32_t AddParam(List** params, Expr* expr) { int32_t index = 0; @@ -298,8 +645,13 @@ public: return index; } + /** @var MOT engine singleton */ static MOT::MOTEngine* m_engine; + + /** @var indicates if MOT engine initialized */ static bool m_initialized; + + /** @var indicates if MOT callbacks has been installed */ static bool m_callbacks_initialized; private: @@ -312,21 +664,76 @@ private: */ static void AddTableColumns(MOT::Table* table, List* tableElts, bool& hasBlob); - static void ValidateCreateIndex(IndexStmt* index, MOT::Table* table, MOT::TxnManager* txn); - + /** + * @brief Performs conversion of varchar value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param colType PG column type + * @param data MOT key placeholder + * @param len MOT placeholder length + * @param oper MOT key operation type + * @param fill MOT fill value + */ static void VarcharToMOTKey(MOT::Column* col, Oid datumType, Datum datum, Oid colType, uint8_t* data, size_t len, KEY_OPER oper, uint8_t fill); + + /** + * @brief Performs conversion of float value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param data MOT key placeholder + */ static void FloatToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data); + + /** + * @brief Performs conversion of decimal value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param data MOT key placeholder + */ static void NumericToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data); + + /** + * @brief Performs conversion of timestamp value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param data MOT key placeholder + */ static void TimestampToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data); + + /** + * @brief Performs conversion of timestamp with time zone value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param data MOT key placeholder + */ static void TimestampTzToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data); + + /** + * @brief Performs conversion of date value from PG to MOT key. + * @param col MOT column + * @param datumType PG data type + * @param datum value + * @param data MOT key placeholder + */ static void DateToMOTKey(MOT::Column* col, Oid datumType, Datum datum, uint8_t* data); }; +/** + * @brief Creates or retrieves MOT transaction manager. + * @param collerSrc function name which has called it + * @param txn_id transaction id + */ inline MOT::TxnManager* GetSafeTxn(const char* callerSrc, ::TransactionId txn_id = 0) { if (!u_sess->mot_cxt.txn_manager) { - MOTAdaptor::InitTxnManager(callerSrc); + // result of InitTxnManager() is put in u_sess->mot_cxt.txn_manager, so no need to check return value + (void)MOTAdaptor::InitTxnManager(callerSrc); if (u_sess->mot_cxt.txn_manager != nullptr) { if (txn_id != 0) { u_sess->mot_cxt.txn_manager->SetTransactionId(txn_id); @@ -338,23 +745,45 @@ inline MOT::TxnManager* GetSafeTxn(const char* callerSrc, ::TransactionId txn_id return u_sess->mot_cxt.txn_manager; } -extern void EnsureSafeThreadAccess(); +/** + * @brief Initializes MOT thread local members. + */ +extern bool EnsureSafeThreadAccess(bool throwError = true); -inline List* BitmapSerialize(List* result, uint8_t* bitmap, int16_t len) +/** + * @brief Serialize bitmap into a PG node list. + * @param result list of nodes + * @param bitmap data containing bitmap + * @param len length of bitmap data + * @param hasIndexUpd indicates if query contains update on indexed column + */ +inline List* BitmapSerialize(List* result, uint8_t* bitmap, int16_t len, MOT::UpdateIndexColumnType hasIndexUpd) { - // set list type to FDW_LIST_BITMAP - result = lappend(result, makeConst(INT4OID, -1, InvalidOid, 4, FDW_LIST_BITMAP, false, true)); - for (int i = 0; i < len; i++) + // set list type to FDW_LIST_TYPE::FDW_LIST_BITMAP + result = lappend( + result, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(FDW_LIST_TYPE::FDW_LIST_BITMAP), false, true)); + result = lappend(result, makeConst(INT1OID, -1, InvalidOid, 1, Int8GetDatum(hasIndexUpd), false, true)); + for (int i = 0; i < len; i++) { result = lappend(result, makeConst(INT1OID, -1, InvalidOid, 1, Int8GetDatum(bitmap[i]), false, true)); + } return result; } -inline void BitmapDeSerialize(uint8_t* bitmap, int16_t len, ListCell** cell) +/** + * @brief De-Serialize list of nodes into a bitmap. + * @param bitmap data containing bitmap + * @param len length of bitmap data + * @param[out] hasIndexUpd indicates if query contains update on indexed column + * @param[in/out] cell start point of nodes that contain bitmap information + */ +inline void BitmapDeSerialize(uint8_t* bitmap, int16_t len, MOT::UpdateIndexColumnType& hasIndexUpd, ListCell** cell) { if (cell != nullptr && *cell != nullptr) { int type = ((Const*)lfirst(*cell))->constvalue; - if (type == FDW_LIST_BITMAP) { + if (type == static_cast(FDW_LIST_TYPE::FDW_LIST_BITMAP)) { + *cell = lnext(*cell); + hasIndexUpd = (MOT::UpdateIndexColumnType)((Const*)lfirst(*cell))->constvalue; *cell = lnext(*cell); for (int i = 0; i < len; i++) { bitmap[i] = (uint8_t)((Const*)lfirst(*cell))->constvalue; @@ -364,33 +793,55 @@ inline void BitmapDeSerialize(uint8_t* bitmap, int16_t len, ListCell** cell) } } +/** + * @brief Destroys scan cursors. + * @param state MOT query state + */ inline void CleanCursors(MOTFdwStateSt* state) { for (int i = 0; i < 2; i++) { if (state->m_cursor[i]) { - state->m_cursor[i]->Invalidate(); - state->m_cursor[i]->Destroy(); - delete state->m_cursor[i]; + if (state->m_currTxn != nullptr) { + std::unordered_map::iterator it = + state->m_currTxn->m_queryState.find((uint64_t)state->m_cursor[i]); + if (it != state->m_currTxn->m_queryState.end()) { + (void)state->m_currTxn->m_queryState.erase((uint64_t)state->m_cursor[i]); + state->m_cursor[i]->Invalidate(); + state->m_cursor[i]->Destroy(); + delete state->m_cursor[i]; + } + } state->m_cursor[i] = NULL; } } } +/** + * @brief Destroys scan cursors. + * @param txn MOT transaction manager + */ inline void CleanQueryStatesOnError(MOT::TxnManager* txn) { if (txn != nullptr) { for (auto& itr : txn->m_queryState) { - MOTFdwStateSt* state = (MOTFdwStateSt*)itr.second; - if (state != nullptr) { - CleanCursors(state); + MOT::IndexIterator* cursor = (MOT::IndexIterator*)itr.second; + if (cursor != nullptr) { + cursor->Invalidate(); + cursor->Destroy(); + delete cursor; } } txn->m_queryState.clear(); } } -MOTFdwStateSt* InitializeFdwState(void* fdwState, List** fdwExpr, uint64_t exTableID); -void* SerializeFdwState(MOTFdwStateSt* state); -void ReleaseFdwState(MOTFdwStateSt* state); +/** + * @brief Returns true in case the transaction has been aborted. + * @param txn MOT transaction manager + */ +inline bool IsTxnInAbortState(MOT::TxnManager* txn) +{ + return txn->IsTxnAborted(); +} #endif // MOT_INTERNAL_H diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.cpp index 2cc4d6699..32dc0a27c 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.cpp @@ -26,53 +26,41 @@ #include "mot_internal.h" #include "nodes/makefuncs.h" -static KEY_OPER keyOperStateMachine[KEY_OPER::READ_INVALID + 1][KEY_OPER::READ_INVALID]; +static KEY_OPER g_keyOperStateMachine[static_cast(KEY_OPER::READ_INVALID) + 1] + [static_cast(KEY_OPER::READ_INVALID)]; void InitKeyOperStateMachine() { // fill key operation matrix - for (uint8_t i = 0; i <= KEY_OPER::READ_INVALID; i++) { - for (uint8_t j = 0; j < KEY_OPER::READ_INVALID; j++) { + for (uint8_t i = 0; i <= static_cast(KEY_OPER::READ_INVALID); i++) { + for (uint8_t j = 0; j < static_cast(KEY_OPER::READ_INVALID); j++) { switch ((KEY_OPER)i) { case KEY_OPER::READ_KEY_EXACT: // = : allows all operations - keyOperStateMachine[i][j] = (KEY_OPER)j; + g_keyOperStateMachine[i][j] = (KEY_OPER)j; break; - case KEY_OPER::READ_KEY_OR_NEXT: // >= : allows =, >, >=, like - keyOperStateMachine[i][j] = - (KEY_OPER)(((KEY_OPER)j) < KEY_OPER::READ_KEY_OR_PREV ? j : KEY_OPER::READ_INVALID); + case KEY_OPER::READ_KEY_OR_NEXT: // >= : allows nothing + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; case KEY_OPER::READ_KEY_AFTER: // > : allows nothing - keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; - case KEY_OPER::READ_KEY_OR_PREV: // <= : allows =, <, <=, like - { - switch ((KEY_OPER)j) { - case KEY_OPER::READ_KEY_EXACT: - case KEY_OPER::READ_KEY_LIKE: - case KEY_OPER::READ_KEY_OR_PREV: - case KEY_OPER::READ_KEY_BEFORE: - keyOperStateMachine[i][j] = (KEY_OPER)j; - break; - default: - keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; - break; - } + case KEY_OPER::READ_KEY_OR_PREV: // <= : allows nothing + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; - } case KEY_OPER::READ_KEY_BEFORE: // < : allows nothing - keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; case KEY_OPER::READ_KEY_LIKE: // like: allows nothing - keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; case KEY_OPER::READ_INVALID: // = : allows all operations - keyOperStateMachine[i][j] = (KEY_OPER)j; + g_keyOperStateMachine[i][j] = (KEY_OPER)j; break; default: - keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; + g_keyOperStateMachine[i][j] = KEY_OPER::READ_INVALID; break; } } @@ -140,8 +128,19 @@ void MatchIndex::ClearPreviousMatch(MOTFdwStateSt* state, bool setLocal, int i, } m_parentColMatch[i][j] = nullptr; + m_colMatch[i][j] = nullptr; + m_opers[i][j] = KEY_OPER::READ_INVALID; m_numMatches[i]--; } + // in case second bound is set, move it to be a first bound + if (i == 0 && m_parentColMatch[1][j] != nullptr) { + m_parentColMatch[0][j] = m_parentColMatch[1][j]; + m_colMatch[0][j] = m_colMatch[1][j]; + m_opers[0][i] = m_opers[1][i]; + m_parentColMatch[1][j] = nullptr; + m_colMatch[1][j] = nullptr; + m_opers[1][j] = KEY_OPER::READ_INVALID; + } } bool MatchIndex::SetIndexColumn( @@ -196,15 +195,15 @@ bool MatchIndex::SetIndexColumn( // in index selection // we can not differentiate between a > 2 and a > 1 if (sameOper) { - if (op == KEY_OPER::READ_KEY_EXACT && m_opers[0][i] != op) { - ClearPreviousMatch(state, setLocal, 0, i); - m_parentColMatch[0][i] = parent; - m_colMatch[0][i] = expr; - m_opers[0][i] = op; - m_numMatches[0]++; + if (op == KEY_OPER::READ_KEY_EXACT && m_opers[1][i] != op) { + ClearPreviousMatch(state, setLocal, 1, i); + m_parentColMatch[1][i] = parent; + m_colMatch[1][i] = expr; + m_opers[1][i] = op; + m_numMatches[1]++; res = true; } else if (m_opers[0][i] != KEY_OPER::READ_KEY_EXACT) { - ClearPreviousMatch(state, setLocal, 0, i); + ClearPreviousMatch(state, setLocal, 1, i); } break; } @@ -214,16 +213,6 @@ bool MatchIndex::SetIndexColumn( } } - if (res && (i < numKeyCols)) { - switch (state->m_table->GetFieldType(cols[i])) { - case MOT::MOT_CATALOG_FIELD_TYPES::MOT_TYPE_CHAR: - case MOT::MOT_CATALOG_FIELD_TYPES::MOT_TYPE_VARCHAR: - res = false; - break; - default: - break; - } - } return res; } @@ -264,10 +253,17 @@ double MatchIndex::GetCost(int numClauses) m_opers[0][i] = KEY_OPER::READ_INVALID; notUsed[0]++; } else if (m_opers[0][i] < KEY_OPER::READ_INVALID) { - KEY_OPER curr = keyOperStateMachine[m_ixOpers[0]][m_opers[0][i]]; + KEY_OPER curr = + g_keyOperStateMachine[static_cast(m_ixOpers[0])][static_cast(m_opers[0][i])]; if (curr < KEY_OPER::READ_INVALID) { - m_ixOpers[0] = curr; + if (m_ixOpers[0] != KEY_OPER::READ_INVALID) { + if (curr > m_ixOpers[0]) { + m_ixOpers[0] = curr; + } + } else { + m_ixOpers[0] = curr; + } used[0]++; if (m_colMatch[1][i] == nullptr && (m_opers[0][i] == KEY_OPER::READ_KEY_EXACT || m_opers[0][i] == KEY_OPER::READ_KEY_LIKE)) { @@ -288,7 +284,8 @@ double MatchIndex::GetCost(int numClauses) m_opers[1][i] = KEY_OPER::READ_INVALID; notUsed[1]++; } else if (m_opers[1][i] < KEY_OPER::READ_INVALID) { - KEY_OPER curr = keyOperStateMachine[m_ixOpers[1]][m_opers[1][i]]; + KEY_OPER curr = + g_keyOperStateMachine[static_cast(m_ixOpers[1])][static_cast(m_opers[1][i])]; if (curr < KEY_OPER::READ_INVALID) { m_ixOpers[1] = curr; @@ -340,7 +337,8 @@ double MatchIndex::GetCost(int numClauses) if (m_colMatch[1][0]) { m_end = 1; if (m_ixOpers[1] == KEY_OPER::READ_PREFIX || m_ixOpers[1] == KEY_OPER::READ_PREFIX_LIKE) { - if (((m_ixOpers[0] & ~KEY_OPER_PREFIX_BITMASK) < KEY_OPER::READ_KEY_OR_PREV)) { + if (((static_cast(m_ixOpers[0]) & ~KEY_OPER_PREFIX_BITMASK) < + static_cast(KEY_OPER::READ_KEY_OR_PREV))) { m_ixOpers[1] = KEY_OPER::READ_PREFIX_OR_PREV; } else { m_ixOpers[1] = KEY_OPER::READ_PREFIX_OR_NEXT; @@ -382,11 +380,11 @@ double MatchIndex::GetCost(int numClauses) bool MatchIndex::AdjustForOrdering(bool desc) { - if (m_end == -1 && m_ixOpers[0] == READ_KEY_EXACT) { // READ_KEY_EXACT + if (m_end == -1 && m_ixOpers[0] == KEY_OPER::READ_KEY_EXACT) { // READ_KEY_EXACT return true; } - KEY_OPER curr = (KEY_OPER)(m_ixOpers[0] & ~KEY_OPER_PREFIX_BITMASK); + KEY_OPER curr = (KEY_OPER)(static_cast(m_ixOpers[0]) & ~KEY_OPER_PREFIX_BITMASK); bool hasBoth = (m_start != -1 && m_end != -1); bool currDesc = !(curr < KEY_OPER::READ_KEY_OR_PREV); @@ -418,6 +416,11 @@ bool MatchIndex::CanApplyOrdering(const int* orderCols) const return true; } + // ordering does not include all index columns from the start + if (m_colMatch[0][i] != nullptr && orderCols[i] == 0) { + return false; + } + // suffix: the order columns are continuation of index columns, we can use index ordering if (m_colMatch[0][i] == nullptr && orderCols[i] == 1) { return true; @@ -440,6 +443,8 @@ void MatchIndex::Serialize(List** list) const ixlist = lappend(ixlist, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(m_start), false, true)); ixlist = lappend(ixlist, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(m_end), false, true)); ixlist = lappend(ixlist, makeConst(INT4OID, -1, InvalidOid, 4, UInt32GetDatum(m_cost), false, true)); + ixlist = lappend(ixlist, makeConst(BOOLOID, -1, InvalidOid, 1, BoolGetDatum(m_fullScan), false, true)); + ixlist = lappend(ixlist, makeConst(INT1OID, -1, InvalidOid, 1, Int8GetDatum(m_order), false, true)); for (int i = 0; i < 2; i++) { ixlist = lappend(ixlist, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(m_numMatches[i]), false, true)); @@ -476,6 +481,12 @@ void MatchIndex::Deserialize(ListCell* cell, uint64_t exTableID) m_cost = (uint32_t)((Const*)lfirst(cell))->constvalue; cell = lnext(cell); + m_fullScan = (bool)((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + + m_order = (SortDir)((Const*)lfirst(cell))->constvalue; + cell = lnext(cell); + for (int i = 0; i < 2; i++) { m_numMatches[i] = (int32_t)((Const*)lfirst(cell))->constvalue; cell = lnext(cell); diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.h b/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.h index fffdc6a10..634ca909b 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.h +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_match_index.h @@ -29,13 +29,11 @@ #include "logger.h" #include "nodes/primnodes.h" -#ifndef MOTFdwStateSt typedef struct MOTFdwState_St MOTFdwStateSt; -#endif -#define KEY_OPER_PREFIX_BITMASK 0x10 +#define KEY_OPER_PREFIX_BITMASK static_cast(0x10) -typedef enum : uint8_t { +enum class KEY_OPER : uint8_t { READ_KEY_EXACT = 0, // equal READ_KEY_LIKE = 1, // like READ_KEY_OR_NEXT = 2, // ge @@ -45,18 +43,20 @@ typedef enum : uint8_t { READ_INVALID = 6, // partial key eq - READ_PREFIX = KEY_OPER_PREFIX_BITMASK | READ_KEY_EXACT, + READ_PREFIX = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_EXACT, // partial key ge - READ_PREFIX_OR_NEXT = KEY_OPER_PREFIX_BITMASK | READ_KEY_OR_NEXT, + READ_PREFIX_OR_NEXT = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_OR_NEXT, // partial key gt - READ_PREFIX_AFTER = KEY_OPER_PREFIX_BITMASK | READ_KEY_AFTER, + READ_PREFIX_AFTER = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_AFTER, // partial key le - READ_PREFIX_OR_PREV = KEY_OPER_PREFIX_BITMASK | READ_KEY_OR_PREV, + READ_PREFIX_OR_PREV = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_OR_PREV, // partial key lt - READ_PREFIX_BEFORE = KEY_OPER_PREFIX_BITMASK | READ_KEY_BEFORE, + READ_PREFIX_BEFORE = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_BEFORE, // partial key like - READ_PREFIX_LIKE = KEY_OPER_PREFIX_BITMASK | READ_KEY_LIKE, -} KEY_OPER; + READ_PREFIX_LIKE = KEY_OPER_PREFIX_BITMASK | KEY_OPER::READ_KEY_LIKE, +}; + +enum class SortDir : uint8_t { SORTDIR_NONE = 0, SORTDIR_ASC = 1, SORTDIR_DESC = 2 }; class MatchIndex { public: @@ -66,7 +66,11 @@ public: } ~MatchIndex() - {} + { + m_remoteConds = nullptr; + m_remoteCondsOrig = nullptr; + m_ix = nullptr; + } List* m_remoteConds = nullptr; List* m_remoteCondsOrig = nullptr; @@ -78,12 +82,14 @@ public: int32_t m_params[2][MAX_KEY_COLUMNS]; int32_t m_numMatches[2] = {0, 0}; double m_costs[2] = {0, 0}; - KEY_OPER m_ixOpers[2] = {READ_INVALID, READ_INVALID}; + KEY_OPER m_ixOpers[2] = {KEY_OPER::READ_INVALID, KEY_OPER::READ_INVALID}; // this is for iteration start condition int32_t m_start = -1; int32_t m_end = -1; double m_cost = 0; + bool m_fullScan = false; + SortDir m_order; void Init() { @@ -91,14 +97,16 @@ public: for (uint j = 0; j < MAX_KEY_COLUMNS; j++) { m_colMatch[i][j] = nullptr; m_parentColMatch[i][j] = nullptr; - m_opers[i][j] = READ_INVALID; + m_opers[i][j] = KEY_OPER::READ_INVALID; m_params[i][j] = -1; } - m_ixOpers[i] = READ_INVALID; + m_ixOpers[i] = KEY_OPER::READ_INVALID; m_costs[i] = 0; m_numMatches[i] = 0; } m_start = m_end = -1; + m_fullScan = false; + m_order = SortDir::SORTDIR_ASC; } bool IsSameOper(KEY_OPER op1, KEY_OPER op2) const; @@ -132,6 +140,7 @@ class MatchIndexArr { public: MatchIndexArr() { + m_ixOid = InvalidOid; for (uint i = 0; i < MAX_NUM_INDEXES; i++) { m_idx[i] = nullptr; } @@ -142,6 +151,7 @@ public: void Clear(bool release = false) { + m_ixOid = InvalidOid; for (uint i = 0; i < MAX_NUM_INDEXES; i++) { if (m_idx[i]) { if (release) { @@ -151,6 +161,7 @@ public: } } } + Oid m_ixOid; MatchIndex* m_idx[MAX_NUM_INDEXES]; }; diff --git a/src/gausskernel/storage/mot/jit_exec/Makefile b/src/gausskernel/storage/mot/jit_exec/Makefile index f056a3feb..05d9a1e29 100644 --- a/src/gausskernel/storage/mot/jit_exec/Makefile +++ b/src/gausskernel/storage/mot/jit_exec/Makefile @@ -35,19 +35,24 @@ ENGINE_INC = ../core include $(top_builddir)/src/Makefile.global OBJ_DIR = ./obj -OBJS = $(OBJ_DIR)/jit_exec.o $(OBJ_DIR)/jit_common.o $(OBJ_DIR)/jit_llvm_query_codegen.o $(OBJ_DIR)/jit_llvm_blocks.o $(OBJ_DIR)/jit_tvm_query_codegen.o $(OBJ_DIR)/jit_tvm_blocks.o $(OBJ_DIR)/jit_helpers.o $(OBJ_DIR)/jit_context.o $(OBJ_DIR)/jit_source.o $(OBJ_DIR)/jit_source_pool.o $(OBJ_DIR)/jit_source_map.o $(OBJ_DIR)/jit_context_pool.o $(OBJ_DIR)/jit_plan.o $(OBJ_DIR)/jit_plan_expr.o $(OBJ_DIR)/jit_explain.o $(OBJ_DIR)/jit_llvm_util.o $(OBJ_DIR)/jit_tvm_util.o $(OBJ_DIR)/jit_llvm.o $(OBJ_DIR)/jit_tvm.o $(OBJ_DIR)/jit_statistics.o -DEPS := $(OBJ_DIR)/jit_exec.d $(OBJ_DIR)/jit_common.d $(OBJ_DIR)/jit_llvm_query_codegen.d $(OBJ_DIR)/jit_llvm_blocks.d $(OBJ_DIR)/jit_tvm_query_codegen.d $(OBJ_DIR)/jit_tvm_blocks.d $(OBJ_DIR)/jit_helpers.d $(OBJ_DIR)/jit_context.d $(OBJ_DIR)/jit_source.d $(OBJ_DIR)/jit_source_pool.d $(OBJ_DIR)/jit_source_map.d $(OBJ_DIR)/jit_context_pool.d $(OBJ_DIR)/jit_plan.d $(OBJ_DIR)/jit_plan_expr.d $(OBJ_DIR)/jit_explain.d $(OBJ_DIR)/jit_llvm_util.d $(OBJ_DIR)/jit_tvm_util.d $(OBJ_DIR)/jit_llvm.d $(OBJ_DIR)/jit_tvm.d $(OBJ_DIR)/jit_statistics.d +SRCS := $(wildcard *.cpp) +OBJS := $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(SRCS)) +DEPS := $(patsubst %.cpp, $(OBJ_DIR)/%.d, $(SRCS)) # Shared library stuff include $(top_srcdir)/src/gausskernel/common.mk override CXXFLAGS += -DMOT_SECURE -I$(top_builddir)/src/gausskernel/storage/mot/jit_exec -I$(top_builddir)/src/gausskernel/storage/mot/fdw_adapter -I$(ENGINE_INC) -I$(ENGINE_INC)/storage -I$(ENGINE_INC)/system -I$(ENGINE_INC)/memory -I$(ENGINE_INC)/memory/garbage_collector -override CXXFLAGS += -I$(ENGINE_INC)/infra -I$(ENGINE_INC)/infra/config -I$(ENGINE_INC)/infra/containers -I$(ENGINE_INC)/infra/stats -I$(ENGINE_INC)/infra/synchronization -I$(ENGINE_INC)/concurrency_control -I$(ENGINE_INC)/storage/index -I$(ENGINE_INC)/system/transaction -I$(ENGINE_INC)/system/common -I$(ENGINE_INC)/system/statistics -I$(ENGINE_INC)/system/transaction_logger -I$(ENGINE_INC)/system/transaction_logger/asynchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/synchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/group_synchronous_redo_log -I$(ENGINE_INC)/system/checkpoint -I$(ENGINE_INC)/system/recovery -I$(ENGINE_INC)/utils +override CXXFLAGS += -I$(ENGINE_INC)/infra -I$(ENGINE_INC)/infra/config -I$(ENGINE_INC)/infra/containers -I$(ENGINE_INC)/infra/stats -I$(ENGINE_INC)/infra/synchronization -I$(ENGINE_INC)/concurrency_control -I$(ENGINE_INC)/storage/index -I$(ENGINE_INC)/storage/sentinel -I$(ENGINE_INC)/system/transaction -I$(ENGINE_INC)/system/common -I$(ENGINE_INC)/system/statistics -I$(ENGINE_INC)/system/transaction_logger -I$(ENGINE_INC)/system/transaction_logger/asynchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/synchronous_redo_log -I$(ENGINE_INC)/system/transaction_logger/group_synchronous_redo_log -I$(ENGINE_INC)/system/checkpoint -I$(ENGINE_INC)/system/recovery -I$(ENGINE_INC)/utils override CXXFLAGS += -faligned-new -fno-rtti override CXXFLAGS += -D__STDC_FORMAT_MACROS +ifeq ($(enable_cassert), yes) +override CXXFLAGS += -DDEBUG +endif + $(OBJS): | buildrepo install: install-data @@ -74,6 +79,11 @@ buildrepo: $(OBJ_DIR)/%.o: %.cpp $(COMPILE.cpp) -MMD -MP -MF"$(patsubst %.o,%.d,$@)" -MT"$@" -o $@ $< +show: + @echo "enable_cassert=${enable_cassert}" + @echo + @echo "CXXFLAGS=${CXXFLAGS}" + ifneq ($(MAKECMDGOALS),clean) -include $(DEPS) endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_common.cpp b/src/gausskernel/storage/mot/jit_exec/jit_common.cpp index 9e45a977d..4d3fad1ba 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_common.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_common.cpp @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * jit_common.cpp - * Definitions used both by LLVM and TVM jitted code. + * Definitions used by LLVM jitted code. * * IDENTIFICATION * src/gausskernel/storage/mot/jit_exec/jit_common.cpp @@ -31,48 +31,49 @@ #include "codegen/gscodegen.h" #include "postgres.h" #include "catalog/pg_operator.h" +#include "utils/plpgsql.h" +#include "catalog/pg_language.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" +#include "utils/builtins.h" +#include "utils/numeric.h" +#include "utils/syscache.h" +#include "utils/lsyscache.h" +#include "storage/mot/jit_exec.h" +#include "commands/proclang.h" + #include "global.h" #include "jit_common.h" #include "utilities.h" #include "jit_plan.h" #include "mm_global_api.h" +#include "catalog_column_types.h" +#include "mot_internal.h" +#include "jit_llvm_query_codegen.h" namespace JitExec { DECLARE_LOGGER(JitCommon, JitExec) -extern JitCommandType ConvertCommandType(int pgCommandType, bool isPKey) +const char* JitRuntimeFaultToString(int faultCode) { - JitCommandType result = JIT_COMMAND_INVALID; + switch (faultCode) { + case JIT_FAULT_INTERNAL_ERROR: + return "Internal Error"; - switch (pgCommandType) { - case CMD_INSERT: - result = JIT_COMMAND_INSERT; - break; - - case CMD_UPDATE: - result = JIT_COMMAND_UPDATE; - break; - - case CMD_DELETE: - result = JIT_COMMAND_DELETE; - break; - - case CMD_SELECT: - result = JIT_COMMAND_SELECT; - break; + case JIT_FAULT_SUB_TX_NOT_CLOSED: + return "Sub-transaction not closed"; default: - break; + return "Unknown generic fault code"; } - - if ((result == JIT_COMMAND_UPDATE) && !isPKey) { - result = JIT_COMMAND_RANGE_UPDATE; - } - - return result; } -extern const char* CommandToString(JitCommandType commandType) +void JitResetCompileState() +{ + JitLlvmResetCompileState(); +} + +const char* CommandToString(JitCommandType commandType) { switch (commandType) { case JIT_COMMAND_INSERT: @@ -87,6 +88,10 @@ extern const char* CommandToString(JitCommandType commandType) return "Range-Update"; case JIT_COMMAND_RANGE_SELECT: return "Range-Select"; + case JIT_COMMAND_RANGE_DELETE: + return "Range-Delete"; + case JIT_COMMAND_FULL_SELECT: + return "Full-Select"; case JIT_COMMAND_AGGREGATE_RANGE_SELECT: return "Aggregate-Range-Select"; case JIT_COMMAND_POINT_JOIN: @@ -97,6 +102,10 @@ extern const char* CommandToString(JitCommandType commandType) return "Aggregate-Range-Join"; case JIT_COMMAND_COMPOUND_SELECT: return "Compound-Select"; + case JIT_COMMAND_FUNCTION: + return "Function"; + case JIT_COMMAND_INVOKE: + return "Invoke"; case JIT_COMMAND_INVALID: default: @@ -104,13 +113,14 @@ extern const char* CommandToString(JitCommandType commandType) } } -extern bool IsRangeCommand(JitCommandType commandType) +bool IsRangeCommand(JitCommandType commandType) { bool rangeCommand = false; switch (commandType) { case JIT_COMMAND_RANGE_UPDATE: case JIT_COMMAND_RANGE_SELECT: + case JIT_COMMAND_RANGE_DELETE: case JIT_COMMAND_AGGREGATE_RANGE_SELECT: case JIT_COMMAND_POINT_JOIN: case JIT_COMMAND_RANGE_JOIN: @@ -126,7 +136,7 @@ extern bool IsRangeCommand(JitCommandType commandType) return rangeCommand; } -extern bool IsJoinCommand(JitCommandType commandType) +bool IsJoinCommand(JitCommandType commandType) { bool joinCommand = false; @@ -145,7 +155,69 @@ extern bool IsJoinCommand(JitCommandType commandType) return joinCommand; } -extern MOT::Table* GetTableFromQuery(const Query* query) +bool IsSelectCommand(JitCommandType commandType) +{ + bool selectCommand = false; + + switch (commandType) { + case JIT_COMMAND_SELECT: + case JIT_COMMAND_RANGE_SELECT: + case JIT_COMMAND_FULL_SELECT: + case JIT_COMMAND_AGGREGATE_RANGE_SELECT: + case JIT_COMMAND_POINT_JOIN: + case JIT_COMMAND_RANGE_JOIN: + case JIT_COMMAND_AGGREGATE_JOIN: + case JIT_COMMAND_COMPOUND_SELECT: + selectCommand = true; + break; + + default: + selectCommand = false; + break; + } + + return selectCommand; +} + +bool IsCommandUsingIndex(JitCommandType commandType) +{ + bool usingIndex = true; + + switch (commandType) { + case JIT_COMMAND_INSERT: + case JIT_COMMAND_FUNCTION: + case JIT_COMMAND_INVOKE: + usingIndex = false; + break; + + default: + break; + } + + return usingIndex; +} + +bool IsWriteCommand(JitCommandType commandType) +{ + bool writeCommand = false; + + switch (commandType) { + case JIT_COMMAND_INSERT: + case JIT_COMMAND_UPDATE: + case JIT_COMMAND_DELETE: + case JIT_COMMAND_RANGE_UPDATE: + case JIT_COMMAND_RANGE_DELETE: + writeCommand = true; + break; + + default: + break; + } + + return writeCommand; +} + +MOT::Table* GetTableFromQuery(const Query* query) { // we reject queries that reference more than one table if (list_length(query->rtable) != 1) { @@ -157,7 +229,9 @@ extern MOT::Table* GetTableFromQuery(const Query* query) Oid relid = rte->relid; MOT_LOG_TRACE("Seeing relid=%u relname=%s", (unsigned)rte->relid, rte->relname); - MOT::Table* table = MOT::GetTableManager()->GetTableByExternal(relid); + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); + MOT::Table* table = currTxn->GetTableByExternalId(relid); if (table != nullptr) { MOT_LOG_DEBUG("Retrieved table %p by external id %u: %s", table, relid, table->GetLongTableName().c_str()); } else { @@ -166,7 +240,7 @@ extern MOT::Table* GetTableFromQuery(const Query* query) return table; } -extern int MapTableColumnToIndex(MOT::Table* table, const MOT::Index* index, int columnId) +int MapTableColumnToIndex(MOT::Table* table, const MOT::Index* index, int columnId) { const int16_t* indexColumnIds = index->GetColumnKeyFields(); int keyColumnCount = index->GetNumFields(); @@ -183,7 +257,7 @@ extern int MapTableColumnToIndex(MOT::Table* table, const MOT::Index* index, int return -1; } -extern int BuildColumnMap(MOT::Table* table, MOT::Index* index, int** columnMap) +int BuildColumnMap(MOT::Table* table, MOT::Index* index, int** columnMap) { int tableColumnCount = (int)table->GetFieldCount(); *columnMap = (int*)MOT::MemSessionAlloc(((uint64_t)tableColumnCount) * sizeof(int)); @@ -200,7 +274,7 @@ extern int BuildColumnMap(MOT::Table* table, MOT::Index* index, int** columnMap) return tableColumnCount; } -extern int BuildIndexColumnOffsets(MOT::Table* table, const MOT::Index* index, int** offsets) +int BuildIndexColumnOffsets(MOT::Table* table, const MOT::Index* index, int** offsets) { const uint16_t* keyLength = index->GetLengthKeyFields(); int indexColumnCount = index->GetNumFields(); @@ -220,7 +294,7 @@ extern int BuildIndexColumnOffsets(MOT::Table* table, const MOT::Index* index, i return indexColumnCount; } -extern bool IsTypeSupported(int resultType) +bool IsTypeSupported(int resultType) { switch (resultType) { case BOOLOID: @@ -236,13 +310,21 @@ extern bool IsTypeSupported(int resultType) case NUMERICOID: case VARCHAROID: case BPCHAROID: + case TEXTOID: + case BYTEAOID: +#ifdef MOT_JIT_ADVANCED_WHERE_OP + case TIMESTAMPTZOID: + case INTERVALOID: + case TINTERVALOID: + case TIMEOID: +#endif return true; default: return false; } } -extern bool IsStringType(int type) +bool IsStringType(int type) { switch (type) { case VARCHAROID: @@ -255,7 +337,7 @@ extern bool IsStringType(int type) } } -extern bool IsPrimitiveType(int type) +bool IsPrimitiveType(int type) { switch (type) { case BOOLOID: @@ -288,11 +370,10 @@ static bool IsEqualsWhereOperator(int whereOp) #ifdef MOT_JIT_ADVANCED_WHERE_OP || (whereOp == INT24EQOID) || (whereOp == INT42EQOID) || (whereOp == INT84EQOID) || (whereOp == INT48EQOID) || (whereOp == INT82EQOID) || (whereOp == INT28EQOID) || (whereOp == FLOAT48EQOID) || (whereOp == FLOAT84EQOID) || - (whereOp == 1108 /* time_eq */) || (whereOp == TIMETZEQOID) || (whereOp == 5550 /* smalldatetime_eq */) || - (whereOp == 2347 /* date_eq_timestamp */) || (whereOp == 2373 /* timestamp_eq_date */) || - (whereOp == 2360 /* date_eq_timestamptz */) || (whereOp == 2386 /* timestamptz_eq_date */) || - (whereOp == 2536 /* timestamp_eq_timestamptz */) || (whereOp == 2542 /* timestamptz_eq_timestamp */) || - (whereOp == INTERVALEQOID) + (whereOp == 1108 /* time_eq */) || (whereOp == TIMETZEQOID) || (whereOp == 2347 /* date_eq_timestamp */) || + (whereOp == 2373 /* timestamp_eq_date */) || (whereOp == 2360 /* date_eq_timestamptz */) || + (whereOp == 2386 /* timestamptz_eq_date */) || (whereOp == 2536 /* timestamp_eq_timestamptz */) || + (whereOp == 2542 /* timestamptz_eq_timestamp */) || (whereOp == INTERVALEQOID) #endif ) { result = true; @@ -314,10 +395,10 @@ static bool IsLessThanWhereOperator(int whereOp) || (whereOp == INT24LTOID) || (whereOp == INT42LTOID) || (whereOp == INT84LTOID) || (whereOp == INT48LTOID) || (whereOp == INT82LTOID) || (whereOp == INT28LTOID) || (whereOp == FLOAT48LTOID) || (whereOp == FLOAT84LTOID) || (whereOp == 1108 /* time_lt */) || (whereOp == 1552 /* timetz_lt */) || - (whereOp == 5550 /* smalldatetime_lt */) || (whereOp == 2347 /* date_lt_timestamp */) || - (whereOp == 2373 /* timestamp_lt_date */) || (whereOp == 2360 /* date_lt_timestamptz */) || - (whereOp == 2386 /* timestamptz_lt_date */) || (whereOp == 2536 /* timestamp_lt_timestamptz */) || - (whereOp == 2542 /* timestamptz_lt_timestamp */) || (whereOp == 1332 /* interval_lt */) + (whereOp == 2347 /* date_lt_timestamp */) || (whereOp == 2373 /* timestamp_lt_date */) || + (whereOp == 2360 /* date_lt_timestamptz */) || (whereOp == 2386 /* timestamptz_lt_date */) || + (whereOp == 2536 /* timestamp_lt_timestamptz */) || (whereOp == 2542 /* timestamptz_lt_timestamp */) || + (whereOp == 1332 /* interval_lt */) #endif ) { result = true; @@ -339,10 +420,10 @@ static bool IsGreaterThanWhereOperator(int whereOp) || (whereOp == INT24GTOID) || (whereOp == INT42GTOID) || (whereOp == INT84GTOID) || (whereOp == INT48GTOID) || (whereOp == INT82GTOID) || (whereOp == INT28GTOID) || (whereOp == FLOAT48GTOID) || (whereOp == FLOAT84GTOID) || (whereOp == 1112 /* time_gt */) || (whereOp == 1554 /* timetz_gt */) || - (whereOp == 5554 /* smalldatetime_gt */) || (whereOp == 2349 /* date_gt_timestamp */) || - (whereOp == 2375 /* timestamp_gt_date */) || (whereOp == 2362 /* date_gt_timestamptz */) || - (whereOp == 2388 /* timestamptz_gt_date */) || (whereOp == 2538 /* timestamp_gt_timestamptz */) || - (whereOp == 2544 /* timestamptz_gt_timestamp */) || (whereOp == 1334 /* interval_gt */) + (whereOp == 2349 /* date_gt_timestamp */) || (whereOp == 2375 /* timestamp_gt_date */) || + (whereOp == 2362 /* date_gt_timestamptz */) || (whereOp == 2388 /* timestamptz_gt_date */) || + (whereOp == 2538 /* timestamp_gt_timestamptz */) || (whereOp == 2544 /* timestamptz_gt_timestamp */) || + (whereOp == 1334 /* interval_gt */) #endif ) { result = true; @@ -364,10 +445,10 @@ static bool IsLessEqualsWhereOperator(int whereOp) || (whereOp == INT24LEOID) || (whereOp == INT42LEOID) || (whereOp == INT84LEOID) || (whereOp == INT48LEOID) || (whereOp == INT82LEOID) || (whereOp == INT28LEOID) || (whereOp == FLOAT48LEOID) || (whereOp == FLOAT84LEOID) || (whereOp == 1111 /* time_le */) || (whereOp == 1553 /* timetz_le */) || - (whereOp == 5553 /* smalldatetime_le */) || (whereOp == 2346 /* date_le_timestamp */) || - (whereOp == 2372 /* timestamp_le_date */) || (whereOp == 2359 /* date_le_timestamptz */) || - (whereOp == 2385 /* timestamptz_le_date */) || (whereOp == 2535 /* timestamp_le_timestamptz */) || - (whereOp == 2541 /* timestamptz_le_timestamp */) || (whereOp == 1333 /* interval_le */) + (whereOp == 2346 /* date_le_timestamp */) || (whereOp == 2372 /* timestamp_le_date */) || + (whereOp == 2359 /* date_le_timestamptz */) || (whereOp == 2385 /* timestamptz_le_date */) || + (whereOp == 2535 /* timestamp_le_timestamptz */) || (whereOp == 2541 /* timestamptz_le_timestamp */) || + (whereOp == 1333 /* interval_le */) #endif ) { result = true; @@ -389,10 +470,10 @@ static bool IsGreaterEqualsWhereOperator(int whereOp) || (whereOp == INT24GEOID) || (whereOp == INT42GEOID) || (whereOp == INT84GEOID) || (whereOp == INT48GEOID) || (whereOp == INT82GEOID) || (whereOp == INT28GEOID) || (whereOp == FLOAT48GEOID) || (whereOp == FLOAT84GEOID) || (whereOp == 1113 /* time_ge */) || (whereOp == 1555 /* timetz_ge */) || - (whereOp == 5549 /* smalldatetime_ge */) || (whereOp == 2348 /* date_ge_timestamp */) || - (whereOp == 2374 /* timestamp_ge_date */) || (whereOp == 2361 /* date_ge_timestamptz */) || - (whereOp == 2387 /* timestamptz_ge_date */) || (whereOp == 2537 /* timestamp_ge_timestamptz */) || - (whereOp == 2543 /* timestamptz_ge_timestamp */) || (whereOp == 1335 /* interval_ge */) + (whereOp == 2348 /* date_ge_timestamp */) || (whereOp == 2374 /* timestamp_ge_date */) || + (whereOp == 2361 /* date_ge_timestamptz */) || (whereOp == 2387 /* timestamptz_ge_date */) || + (whereOp == 2537 /* timestamp_ge_timestamptz */) || (whereOp == 2543 /* timestamptz_ge_timestamp */) || + (whereOp == 1335 /* interval_ge */) #endif ) { result = true; @@ -401,12 +482,12 @@ static bool IsGreaterEqualsWhereOperator(int whereOp) return result; } -extern bool IsWhereOperatorSupported(int whereOp) +bool IsWhereOperatorSupported(int whereOp) { return ClassifyWhereOperator(whereOp) != JIT_WOC_INVALID; } -extern JitWhereOperatorClass ClassifyWhereOperator(int whereOp) +JitWhereOperatorClass ClassifyWhereOperator(int whereOp) { JitWhereOperatorClass result = JIT_WOC_INVALID; @@ -425,44 +506,12 @@ extern JitWhereOperatorClass ClassifyWhereOperator(int whereOp) return result; } -#define APPLY_UNARY_OPERATOR(funcId, name) \ - case funcId: \ - break; -#define APPLY_BINARY_OPERATOR(funcId, name) \ - case funcId: \ - break; -#define APPLY_TERNARY_OPERATOR(funcId, name) \ - case funcId: \ - break; -#define APPLY_UNARY_CAST_OPERATOR(funcId, name) \ - case funcId: \ - break; -#define APPLY_BINARY_CAST_OPERATOR(funcId, name) \ - case funcId: \ - break; -#define APPLY_TERNARY_CAST_OPERATOR(funcId, name) \ - case funcId: \ - break; - -extern bool IsFuncIdSupported(int funcId) +bool IsFuncIdSupported(int funcId) { - bool result = true; - switch (funcId) { - APPLY_OPERATORS() - default: - result = false; - } - return result; + return true; } -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -extern bool IsFullPrefixSearch(const int* columnArray, int columnCount, int* firstZeroColumn) +bool IsFullPrefixSearch(const int* columnArray, int columnCount, int* firstZeroColumn) { *firstZeroColumn = -1; for (int i = 0; i < columnCount; ++i) { @@ -479,7 +528,43 @@ extern bool IsFullPrefixSearch(const int* columnArray, int columnCount, int* fir return true; } -extern JitQuerySortOrder GetQuerySortOrder(const Query* query) +JitQuerySortOrder ClassifyOperatorSortOrder(Oid op) +{ + JitQuerySortOrder result = JIT_QUERY_SORT_INVALID; + + MOT_LOG_TRACE("Checking sort order by operator %u", op); + + bool ascending = ((op == INT48LTOID) || (op == 58) /* boolean less than */ || (op == INT2LTOID) || + (op == INT4LTOID) || (op == INT8LTOID) || (op == INT84LTOID) || (op == INT24LTOID) || + (op == INT42LTOID) || (op == INT28LTOID) || (op == INT82LTOID) || (op == 5515) /* int1lt */ || + (op == FLOAT4LTOID) || (op == FLOAT8LTOID) || (op == FLOAT48LTOID) || (op == FLOAT84LTOID) || + (op == NUMERICLTOID) || (op == 631) /* char less than */ || (op == TEXTLTOID) || + (op == BPCHARLTOID) || (op == 1957) /* bytealt */ || (op == DATELTOID) || + (op == 1110) /* time_lt */ || (op == 1552) /* timetz_lt */ || (op == 1322) /* timestamptz_lt */ || + (op == 1332) /* interval_lt */ || (op == TIMESTAMPLTOID) || (op == 5552) /* smalldatetime_lt */); + + bool descending = ((op == INT48GTOID) || (op == 59) /* boolgt */ || (op == INT2GTOID) || (op == INT4GTOID) || + (op == INT8GTOID) || (op == INT84GTOID) || (op == INT24GTOID) || (op == INT42GTOID) || + (op == INT28GTOID) || (op == INT82GTOID) || (op == 5517) /* int1gt */ || (op == FLOAT4GTOID) || + (op == FLOAT8GTOID) || (op == FLOAT48GTOID) || (op == FLOAT84GTOID) || (op == NUMERICGTOID) || + (op == 633) /* chargt */ || (op == TEXTGTOID) || (op == BPCHARGTOID) || + (op == 1959) /* byteagt */ || (op == DATEGTOID) || (op == 1112) /* time_gt */ || + (op == 1554) /* timetz_gt */ || (op == 1324) /* timestamptz_gt */ || + (op == 1334) /* interval_gt */ || (op == TIMESTAMPGTOID) || (op == 5554) /* smalldatetime_gt */); + + if (ascending) { + MOT_LOG_TRACE("Found ascending sort order by sortop %u", op); + result = JIT_QUERY_SORT_ASCENDING; + } else if (descending) { + MOT_LOG_TRACE("Found descending sort order by sortop %u", op); + result = JIT_QUERY_SORT_DESCENDING; + } else { + MOT_LOG_TRACE("Could not classify sort order by operator %u", op); + } + return result; +} + +JitQuerySortOrder GetQuerySortOrder(const Query* query) { JitQuerySortOrder result = JIT_QUERY_SORT_INVALID; @@ -488,34 +573,13 @@ extern JitQuerySortOrder GetQuerySortOrder(const Query* query) result = JIT_QUERY_SORT_ASCENDING; } else { SortGroupClause* sgc = (SortGroupClause*)linitial(query->sortClause); - int op = (int)sgc->sortop; - MOT_LOG_TRACE("Checking sort order by operator %d", op); - if ((op == INT48LTOID) || (op == 58) /* boolean less than */ || (op == INT2LTOID) || (op == INT4LTOID) || - (op == INT8LTOID) || (op == INT84LTOID) || (op == INT24LTOID) || (op == INT42LTOID) || (op == INT28LTOID) || - (op == INT82LTOID) || (op == 5515) /* int1lt */ || (op == FLOAT4LTOID) || (op == FLOAT8LTOID) || - (op == FLOAT48LTOID) || (op == FLOAT84LTOID) || (op == NUMERICLTOID) || (op == 631) /* char less than */ || - (op == TEXTLTOID) || (op == BPCHARLTOID) || (op == 1957) /* bytealt */ || (op == DATELTOID) || - (op == 1110) /* time_lt */ || (op == 1552) /* timetz_lt */ || (op == 1322) /* timestamptz_lt */ || - (op == 1332) /* interval_lt */ || (op == TIMESTAMPLTOID) || (op == 5552) /* smalldatetime_lt */) { - MOT_LOG_TRACE("Found ascending sort order by sortop %d", op); - result = JIT_QUERY_SORT_ASCENDING; - } else if ((op == INT48GTOID) || (op == 59) /* boolgt */ || (op == INT2GTOID) || (op == INT4GTOID) || - (op == INT8GTOID) || (op == INT84GTOID) || (op == INT24GTOID) || (op == INT42GTOID) || - (op == INT28GTOID) || (op == INT82GTOID) || (op == 5517) /* int1gt */ || (op == FLOAT4GTOID) || - (op == FLOAT8GTOID) || (op == FLOAT48GTOID) || (op == FLOAT84GTOID) || (op == NUMERICGTOID) || - (op == 633) /* chargt */ || (op == TEXTGTOID) || (op == BPCHARGTOID) || (op == 1959) /* byteagt */ || - (op == DATEGTOID) || (op == 1112) /* time_gt */ || (op == 1554) /* timetz_gt */ || - (op == 1324) /* timestamptz_gt */ || (op == 1334) /* interval_gt */ || (op == TIMESTAMPGTOID) || - (op == 5554) /* smalldatetime_gt */) { - MOT_LOG_TRACE("Found descending sort order by sortop %d", op); - result = JIT_QUERY_SORT_DESCENDING; - } + result = ClassifyOperatorSortOrder(sgc->sortop); } return result; } -extern int GetAggregateTupleColumnIdAndType(const Query* query, int& zeroType) +int GetAggregateTupleColumnIdAndType(const Query* query, int& zeroType) { TargetEntry* targetEntry = (TargetEntry*)linitial(query->targetList); int tupleColId = targetEntry->resno - 1; @@ -526,7 +590,7 @@ extern int GetAggregateTupleColumnIdAndType(const Query* query, int& zeroType) } /** @brief Initializes a table info struct. */ -extern bool InitTableInfo(TableInfo* tableInfo, MOT::Table* table, MOT::Index* index) +bool InitTableInfo(TableInfo* tableInfo, MOT::Table* table, MOT::Index* index) { tableInfo->m_table = table; tableInfo->m_index = index; @@ -540,6 +604,7 @@ extern bool InitTableInfo(TableInfo* tableInfo, MOT::Table* table, MOT::Index* i tableInfo->m_indexColumnCount = BuildIndexColumnOffsets(table, index, &tableInfo->m_indexColumnOffsets); if (tableInfo->m_indexColumnOffsets == nullptr) { MOT::MemSessionFree(tableInfo->m_columnMap); + tableInfo->m_columnMap = nullptr; return false; } @@ -547,28 +612,30 @@ extern bool InitTableInfo(TableInfo* tableInfo, MOT::Table* table, MOT::Index* i } /** @brief Releases all resources associated with a table info struct. */ -extern void DestroyTableInfo(TableInfo* tableInfo) +void DestroyTableInfo(TableInfo* tableInfo) { if (tableInfo != nullptr) { if (tableInfo->m_columnMap != nullptr) { MOT::MemSessionFree(tableInfo->m_columnMap); + tableInfo->m_columnMap = nullptr; } if (tableInfo->m_indexColumnOffsets != nullptr) { MOT::MemSessionFree(tableInfo->m_indexColumnOffsets); + tableInfo->m_indexColumnOffsets = nullptr; } } } -extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan) +bool PrepareSubQueryData(JitQueryContext* jitContext, JitCompoundPlan* plan) { bool result = true; // allocate sub-query data array int subQueryCount = plan->_sub_query_count; MOT_LOG_TRACE("Preparing %u sub-query data items", subQueryCount); - uint32_t allocSize = sizeof(JitContext::SubQueryData) * subQueryCount; - jitContext->m_subQueryData = (JitContext::SubQueryData*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); - if (jitContext->m_subQueryData == nullptr) { + uint32_t allocSize = sizeof(JitSubQueryContext) * subQueryCount; + jitContext->m_subQueryContext = (JitSubQueryContext*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); + if (jitContext->m_subQueryContext == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT code", "Failed to allocate %u bytes for %d sub-query data items in JIT context object", @@ -586,13 +653,7 @@ extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan) int subQueryIndex = subLinkExpr->_sub_query_index; MOT_ASSERT(subQueryIndex < subQueryCount); MOT_LOG_TRACE("Preparing sub-query %u data", subQueryIndex); - JitContext::SubQueryData* subQueryData = &jitContext->m_subQueryData[subQueryIndex]; - - // nullify unknown members - subQueryData->m_tupleDesc = nullptr; - subQueryData->m_slot = nullptr; - subQueryData->m_searchKey = nullptr; - subQueryData->m_endIteratorKey = nullptr; + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[subQueryIndex]; // prepare sub-query data items according to sub-plan type JitPlan* subPlan = plan->_sub_query_plans[subQueryIndex]; @@ -604,12 +665,14 @@ extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan) JitPointQueryPlan* subPointQueryPlan = (JitPointQueryPlan*)subPlan; if (subPointQueryPlan->_command_type == JIT_COMMAND_SELECT) { MOT_LOG_TRACE("Detected point-query select sub-plan"); - subQueryData->m_commandType = JIT_COMMAND_SELECT; + subQueryContext->m_commandType = JIT_COMMAND_SELECT; JitSelectPlan* selectPlan = (JitSelectPlan*)subPlan; - subQueryData->m_table = selectPlan->_query._table; - subQueryData->m_index = subQueryData->m_table->GetPrimaryIndex(); - subQueryData->m_indexId = subQueryData->m_index->GetExtId(); - MOT_LOG_TRACE("Installed sub-query %d index id: %" PRIu64, i, subQueryData->m_indexId); + subQueryContext->m_table = selectPlan->_query._table; + subQueryContext->m_tableId = subQueryContext->m_table->GetTableExId(); + MOT_LOG_TRACE("Installed sub-query %d table id: %" PRIu64, i, subQueryContext->m_tableId); + subQueryContext->m_index = subQueryContext->m_table->GetPrimaryIndex(); + subQueryContext->m_indexId = subQueryContext->m_index->GetExtId(); + MOT_LOG_TRACE("Installed sub-query %d index id: %" PRIu64, i, subQueryContext->m_indexId); } else { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Generate JIT code", @@ -624,12 +687,12 @@ extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan) if (subRangeScanPlan->_command_type == JIT_COMMAND_SELECT) { MOT_LOG_TRACE("Detected range-scan select sub-plan"); JitRangeSelectPlan* rangeSelectPlan = (JitRangeSelectPlan*)subPlan; - subQueryData->m_table = rangeSelectPlan->_index_scan._table; - subQueryData->m_index = subQueryData->m_table->GetIndex(rangeSelectPlan->_index_scan._index_id); - if (rangeSelectPlan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { // aggregate range-scan - subQueryData->m_commandType = JIT_COMMAND_AGGREGATE_RANGE_SELECT; + subQueryContext->m_table = rangeSelectPlan->_index_scan._table; + subQueryContext->m_index = rangeSelectPlan->_index_scan._index; + if (rangeSelectPlan->m_aggCount > 0) { // aggregate range-scan + subQueryContext->m_commandType = JIT_COMMAND_AGGREGATE_RANGE_SELECT; } else if (rangeSelectPlan->_limit_count == 1) { - subQueryData->m_commandType = JIT_COMMAND_RANGE_SELECT; // implies "limit 1" clause + subQueryContext->m_commandType = JIT_COMMAND_RANGE_SELECT; // implies "limit 1" clause } else { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Generate JIT code", @@ -658,6 +721,44 @@ extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan) return result; } +const char* ExtractFunctionName(Node* parseTree) +{ + const char* funcName = nullptr; + + // function invocation is in the form: "SELECT (args)" + if (parseTree->type != T_SelectStmt) { + MOT_LOG_TRACE("Cannot extract function name from parse tree: not a SELECT statement"); + } else { + SelectStmt* selectStmt = (SelectStmt*)parseTree; + // now check target list + if (list_length(selectStmt->targetList) != 1) { + MOT_LOG_TRACE( + "Cannot extract function name from parse tree: target list does not contain exactly one item"); + } else { + ResTarget* resTarget = (ResTarget*)linitial(selectStmt->targetList); + if (resTarget->indirection) { + MOT_LOG_TRACE("Cannot extract function name from parse tree: result target contains indirection"); + } else { + if (resTarget->val->type != T_FuncCall) { + MOT_LOG_TRACE("Cannot extract function name from parse tree: result target value type is not a " + "function call"); + } else { + FuncCall* funcCall = (FuncCall*)resTarget->val; + Value* value = (Value*)linitial(funcCall->funcname); + if (value->type != T_String) { + MOT_LOG_TRACE("Cannot extract function name from parse tree: function call name of result " + "target does not having a string value"); + } else { + funcName = value->val.str; + } + } + } + } + } + + return funcName; +} + static bool CloneStringDatum(Datum source, Datum* target, JitContextUsage usage) { bytea* value = DatumGetByteaP(source); @@ -671,12 +772,7 @@ static bool CloneStringDatum(Datum source, Datum* target, JitContextUsage usage) size_t strSize = len - VARHDRSZ; MOT_LOG_TRACE("CloneStringDatum(): len = %u, src = %*.*s", (unsigned)len, strSize, strSize, src); - bytea* copy = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - copy = (bytea*)MOT::MemGlobalAlloc(len); - } else { - copy = (bytea*)MOT::MemSessionAlloc(len); - } + bytea* copy = (bytea*)JitMemAlloc(len, usage); if (copy == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum string constant", (unsigned)len); @@ -698,12 +794,7 @@ static bool CloneTimeTzDatum(Datum source, Datum* target, JitContextUsage usage) { MOT::TimetzSt* value = (MOT::TimetzSt*)DatumGetPointer(source); size_t allocSize = sizeof(MOT::TimetzSt); - MOT::TimetzSt* copy = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - copy = (MOT::TimetzSt*)MOT::MemGlobalAlloc(allocSize); - } else { - copy = (MOT::TimetzSt*)MOT::MemSessionAlloc(allocSize); - } + MOT::TimetzSt* copy = (MOT::TimetzSt*)JitMemAlloc(allocSize, usage); if (copy == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum TimeTZ constant", (unsigned)allocSize); @@ -720,12 +811,7 @@ static bool CloneIntervalDatum(Datum source, Datum* target, JitContextUsage usag { MOT::IntervalSt* value = (MOT::IntervalSt*)DatumGetPointer(source); size_t allocSize = sizeof(MOT::IntervalSt); - MOT::IntervalSt* copy = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - copy = (MOT::IntervalSt*)MOT::MemGlobalAlloc(allocSize); - } else { - copy = (MOT::IntervalSt*)MOT::MemSessionAlloc(allocSize); - } + MOT::IntervalSt* copy = (MOT::IntervalSt*)JitMemAlloc(allocSize, usage); if (copy == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", @@ -745,12 +831,7 @@ static bool CloneTIntervalDatum(Datum source, Datum* target, JitContextUsage usa { MOT::TintervalSt* value = (MOT::TintervalSt*)DatumGetPointer(source); size_t allocSize = sizeof(MOT::TintervalSt); - MOT::TintervalSt* copy = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - copy = (MOT::TintervalSt*)MOT::MemGlobalAlloc(allocSize); - } else { - copy = (MOT::TintervalSt*)MOT::MemSessionAlloc(allocSize); - } + MOT::TintervalSt* copy = (MOT::TintervalSt*)JitMemAlloc(allocSize, usage); if (copy == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", @@ -770,12 +851,7 @@ static bool CloneNumericDatum(Datum source, Datum* target, JitContextUsage usage { varlena* var = (varlena*)DatumGetPointer(source); Size len = VARSIZE(var); - struct varlena* result = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - result = (varlena*)MOT::MemGlobalAlloc(len); - } else { - result = (varlena*)MOT::MemSessionAlloc(len); - } + struct varlena* result = (varlena*)JitMemAlloc(len, usage); if (result == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum Numeric constant", (unsigned)len); @@ -794,12 +870,7 @@ static bool CloneCStringDatum(Datum source, Datum* target, JitContextUsage usage char* src = DatumGetCString(source); size_t len = strlen(src) + 1; // includes terminating null - char* copy = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - copy = (char*)MOT::MemGlobalAlloc(len); - } else { - copy = (char*)MOT::MemSessionAlloc(len); - } + char* copy = (char*)JitMemAlloc(len, usage); if (copy == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum string constant", (unsigned)len); @@ -813,7 +884,7 @@ static bool CloneCStringDatum(Datum source, Datum* target, JitContextUsage usage return true; } -extern bool CloneDatum(Datum source, int type, Datum* target, JitContextUsage usage) +bool CloneDatum(Datum source, int type, Datum* target, JitContextUsage usage) { bool result = true; if (IsStringType(type)) { @@ -850,7 +921,7 @@ extern bool CloneDatum(Datum source, int type, Datum* target, JitContextUsage us return result; } -extern bool PrepareDatumArray(Const* constArray, uint32_t constCount, JitDatumArray* datumArray) +bool PrepareDatumArray(Const* constArray, uint32_t constCount, JitDatumArray* datumArray) { size_t allocSize = sizeof(JitDatum) * constCount; JitDatum* datums = (JitDatum*)MOT::MemGlobalAlloc(allocSize); @@ -887,4 +958,795 @@ extern bool PrepareDatumArray(Const* constArray, uint32_t constCount, JitDatumAr datumArray->m_datums = datums; return true; } + +static void PrintNumeric(MOT::LogLevel logLevel, Datum value) +{ + Datum result = DirectFunctionCall1(numeric_out, value); + char* cstring = DatumGetCString(result); + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[numeric] %s", cstring); +} + +static void PrintVarchar(MOT::LogLevel logLevel, Datum value) +{ + bytea* txt = DatumGetByteaP(value); + size_t size = VARSIZE(txt); // includes header len VARHDRSZ + char* src = VARDATA(txt); + size_t strSize = size - VARHDRSZ; + MOT_LOG_APPEND(logLevel, "[text %u] %.*s", (unsigned)strSize, (int)strSize, src); +} + +void PrintDatum(MOT::LogLevel logLevel, Oid ptype, Datum datum, bool isnull) +{ + if (isnull) { + MOT_LOG_APPEND(logLevel, "[type %u] NULL", ptype); + } else if (ptype == NUMERICOID) { + PrintNumeric(logLevel, datum); + } else if ((ptype == VARCHAROID) || (ptype == BPCHAROID) || (ptype == TEXTOID)) { + PrintVarchar(logLevel, datum); + } else { + switch (ptype) { + case BOOLOID: + MOT_LOG_APPEND(logLevel, "[bool] %s", (unsigned)DatumGetBool(datum) ? "true" : "false"); + break; + + case CHAROID: + MOT_LOG_APPEND(logLevel, "[char] %c", (char)DatumGetChar(datum)); + break; + + case INT1OID: + MOT_LOG_APPEND(logLevel, "[int1] %u", (unsigned)DatumGetUInt8(datum)); + break; + + case INT2OID: + MOT_LOG_APPEND(logLevel, "[int2] %d", (int)DatumGetInt16(datum)); + break; + + case INT4OID: + MOT_LOG_APPEND(logLevel, "[int4] %d", (int)DatumGetInt32(datum)); + break; + + case INT8OID: + MOT_LOG_APPEND(logLevel, "[int8] %" PRId64, (int64_t)DatumGetInt64(datum)); + break; + + case TIMESTAMPOID: + MOT_LOG_APPEND(logLevel, "[timestamp] %" PRIu64, (uint64_t)DatumGetTimestamp(datum)); + break; + + case TIMESTAMPTZOID: + MOT_LOG_APPEND(logLevel, "[timestamptz] %" PRId64, (int64_t)DatumGetTimestampTz(datum)); + break; + + case FLOAT4OID: + MOT_LOG_APPEND(logLevel, "[float4] %f", (double)DatumGetFloat4(datum)); + break; + + case FLOAT8OID: + MOT_LOG_APPEND(logLevel, "[float8] %f", (double)DatumGetFloat8(datum)); + break; + +#ifdef MOT_JIT_ADVANCED_WHERE_OP + case TIMETZOID: { + MOT::TimetzSt* timetzSt = (MOT::TimetzSt*)DatumGetPointer(datum); + MOT_LOG_APPEND(logLevel, "[timetz] time=%" PRIu64 ", zone=%u", timetzSt->m_time, timetzSt->m_zone); + break; + } + + case INTERVALOID: { + MOT::IntervalSt* intervalSt = (MOT::IntervalSt*)DatumGetPointer(datum); + MOT_LOG_APPEND(logLevel, + "[interval] time=%" PRIu64 ", days=%u, months=%u", + intervalSt->m_time, + intervalSt->m_day, + intervalSt->m_month); + break; + } + + case TINTERVALOID: { + MOT::TintervalSt* tintervalSt = (MOT::TintervalSt*)DatumGetPointer(datum); + MOT_LOG_APPEND(logLevel, + "[tinterval] status=%u, data[0]=%u, data[1]=%u", + tintervalSt->m_status, + tintervalSt->m_data[0], + tintervalSt->m_data[1]); + break; + } +#endif + + default: + MOT_LOG_APPEND(logLevel, "[type %u] %" PRIu64, ptype, (uint64_t)datum); + break; + } + } +} + +double NumericToDouble(Datum numeric_value) +{ + return (double)DatumGetFloat8(DirectFunctionCall1(numeric_float8, numeric_value)); +} + +void PrepareExecState(PLpgSQL_execstate* estate, PLpgSQL_function* function) +{ + // plpgsql_estate_setup() does way too much, we just need the function pointer and the datum array + estate->func = function; + estate->ndatums = function->ndatums; + estate->datums = function->datums; +} + +bool GetExprQueryAttrs(PLpgSQL_expr* expr, PLpgSQL_function* function, ExprQueryAttrs* attrs) +{ + char* queryString = expr->query; + MOT_LOG_TRACE("Retrieving query attributes: %s", queryString); + + expr->func = function; + // Set pre-parse trigger to true. This is required for make_datum_param during non-jittable query cached plan + // revalidation, otherwise we need to setup estate datums (or dump core) + volatile bool preParseTrigger = expr->func->pre_parse_trig; + expr->func->pre_parse_trig = true; + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + u_sess->mot_cxt.jit_parse_error = 0; + u_sess->mot_cxt.jit_codegen_error = 0; + volatile bool result = false; + volatile SPIPlanPtr spiPlan = nullptr; + // in order to avoid life-cycle management, we use the session-level context + volatile MemoryContext oldCtx = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); + PG_TRY(); + { + spiPlan = SPI_prepare_params(queryString, (ParserSetupHook)plpgsql_parser_setup, (void*)expr, 0); + if (spiPlan == nullptr) { + if (u_sess->mot_cxt.jit_parse_error == MOT_JIT_TABLE_NOT_FOUND) { + MOT_LOG_TRACE("Failed to parse query, table not found: %s", queryString); + } else if (u_sess->mot_cxt.jit_parse_error == MOT_JIT_GENERIC_PARSE_ERROR) { + MOT_LOG_TRACE("Failed to parse query, unknown error: %s", queryString); + } else { + MOT_LOG_TRACE("Failed to prepare parameters for expression, SPI error: %s (%d)", + SPI_result_code_string(SPI_result), + (int)SPI_result); + } + } else { + // get single plan source from plan + List* planList = SPI_plan_get_plan_sources(spiPlan); + if (list_length(planList) != 1) { + MOT_LOG_TRACE("Unexpected plan list length %d, disqualifying function", list_length(planList)); + (void)SPI_freeplan(spiPlan); + spiPlan = nullptr; + } else { + CachedPlanSource* planSource = (CachedPlanSource*)linitial(planList); + + // get single query from plan source + if (list_length(planSource->query_list) != 1) { + (void)SPI_freeplan(spiPlan); + spiPlan = nullptr; + MOT_LOG_TRACE("Unexpected query list length, disqualifying function"); + } else { + Query* query = (Query*)linitial(planSource->query_list); + + attrs->m_spiPlan = spiPlan; + attrs->m_planSource = planSource; + attrs->m_query = query; + if (expr->paramnos != nullptr) { + attrs->m_paramNos = bms_copy(expr->paramnos); + } else { + attrs->m_paramNos = nullptr; + } + result = true; + } + } + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(oldCtx); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while retrieving query attributes: %s", edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while retrieving query attributes: %s", edata->message), + errdetail("%s", edata->detail))); + u_sess->mot_cxt.jit_codegen_error = edata->sqlerrcode; + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + (void)MemoryContextSwitchTo(oldCtx); + + if (!result) { + if (spiPlan != nullptr) { + (void)SPI_freeplan(spiPlan); + } + } + expr->func->pre_parse_trig = preParseTrigger; + return result; +} + +void CleanupExprQueryAttrs(ExprQueryAttrs* attrs) +{ + if (attrs->m_spiPlan != nullptr) { + (void)SPI_freeplan(attrs->m_spiPlan); + attrs->m_spiPlan = nullptr; + } + if (attrs->m_paramNos != nullptr) { + bms_free(attrs->m_paramNos); + attrs->m_paramNos = nullptr; + } +} + +ParamListInfo CreateParamListInfo(int paramCount, bool isGlobalUsage) +{ + size_t allocSize = sizeof(ParamListInfoData) + sizeof(ParamExternData) * paramCount; + ParamListInfo result = nullptr; + if (isGlobalUsage) { + result = (ParamListInfo)MOT::MemGlobalAlloc(allocSize); + } else { + result = (ParamListInfo)MOT::MemSessionAlloc(allocSize); + } + if (result != nullptr) { + errno_t erc = memset_s(result, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + result->numParams = paramCount; + } + return result; +} + +static inline const char* FormatJitTime(char* buffer, size_t len) +{ + // get current time + struct tm* tmInfo; + struct timeval tv; + struct tm localTime; + (void)gettimeofday(&tv, NULL); + + long int millisec = lrint(tv.tv_usec / 1000.0); // Round to nearest millisec + if (millisec >= 1000) { // Allow for rounding up to nearest second + millisec -= 1000; + tv.tv_sec++; + } + + tmInfo = localtime_r(&tv.tv_sec, &localTime); + + // format time + size_t offset = strftime(buffer, len, "%Y-%m-%d %H:%M:%S", tmInfo); + errno_t erc = snprintf_s(buffer + offset, len - offset, len - offset - 1, ".%03d", (int)millisec); + securec_check_ss(erc, "\0", "\0"); + return buffer; +} + +inline PLpgSQL_function* CompileFunction(Oid functionOid) +{ + // fill-in the minimum required for compiling + FmgrInfo flinfo = {}; + errno_t errorno = memset_s(&flinfo, sizeof(flinfo), 0, sizeof(flinfo)); + securec_check(errorno, "", ""); + + flinfo.fn_oid = functionOid; + flinfo.fn_mcxt = CurrentMemoryContext; + + Datum args[1]; + FunctionCallInfoData fakeInfo = {}; + errorno = memset_s(&fakeInfo, sizeof(fakeInfo), 0, sizeof(fakeInfo)); + securec_check(errorno, "", ""); + + fakeInfo.fncollation = DEFAULT_COLLATION_OID; + fakeInfo.flinfo = &flinfo; + fakeInfo.arg = args; + fakeInfo.arg[0] = Int32GetDatum(functionOid); + + // must call PG initialization for current session first + _PG_init(); + return plpgsql_compile(&fakeInfo, false); +} + +static PLpgSQL_function* TriggerFunctionCompilation(const char* functionName, Oid functionOid) +{ + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile PLpgSQL_function* result = nullptr; + volatile HeapTuple procTuple = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + + PG_TRY(); + { + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + if (procTuple == nullptr) { + MOT_LOG_TRACE("Cannot find function by id: %u", (unsigned)functionOid); + } else { + Form_pg_proc procStruct = (Form_pg_proc)GETSTRUCT(procTuple); + MOT_LOG_TRACE("Function %u language id: %u", (unsigned)functionOid, (unsigned)procStruct->prolang); + if (procStruct->prolang != get_language_oid("plpgsql", false)) { + MOT_LOG_TRACE("Skipping trigger compilation for function %u: not PLpgSQL", (unsigned)functionOid); + } else { + MOT_LOG_TRACE("Triggering validation of function %s by id %u", functionName, (unsigned)functionOid); + result = CompileFunction(functionOid); + } + ReleaseSysCache(procTuple); + procTuple = nullptr; + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while triggering compilation for function %s with id %u: %s", + functionName, + functionOid, + edata->message); + ereport(WARNING, + (errmsg("Failed to trigger compilation for function '%s' for MOT jitted execution.", functionName), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + + // even though resource owner will take care of this, we want to clean up early + if (procTuple != nullptr) { + ReleaseSysCache(procTuple); + } + } + PG_END_TRY(); + return (PLpgSQL_function*)result; +} + +PLpgSQL_function* GetPGCompiledFunction(Oid functionOid, const char* funcName /*= nullptr */) +{ + // just trigger compilation for the function and collect the result. + MOT_LOG_TRACE("Retrieving compiled function %u", functionOid); + if (funcName == nullptr) { + funcName = get_func_name(functionOid); + } + if (funcName == nullptr) { + MOT_LOG_TRACE("GetPGCompiledFunction(): Cannot find function with id %u", (unsigned)functionOid); + return nullptr; + } + PLpgSQL_function* func = TriggerFunctionCompilation(funcName, functionOid); + if (func == nullptr) { + MOT_LOG_TRACE("Failed to trigger function %u compilation", functionOid); + } else { + MOT_LOG_TRACE("Collected function %p by id %u with txn id %" PRIu64, func, functionOid, func->fn_xmin); + } + return func; +} + +void SqlStateToCode(int sqlState, char* sqlStateCode) +{ + for (int i = 0; i < 5; ++i) { + sqlStateCode[i] = PGUNSIXBIT(sqlState); + sqlState = sqlState >> 6; + } + sqlStateCode[5] = 0; +} + +void RaiseEreport(int sqlState, const char* errorMessage, const char* errorDetail, const char* errorHint) +{ + if (errorDetail && errorHint) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(sqlState), + errmsg("%s", errorMessage), + errdetail("%s", errorDetail), + errhint("%s", errorHint))); + } else if (errorDetail) { + ereport( + ERROR, (errmodule(MOD_MOT), errcode(sqlState), errmsg("%s", errorMessage), errdetail("%s", errorDetail))); + } else { + ereport(ERROR, (errmodule(MOD_MOT), errcode(sqlState), errmsg("%s", errorMessage))); + } +} + +char* DupString(const char* source, JitContextUsage usage) +{ + size_t len = strlen(source); + char* result = (char*)JitMemAlloc(len + 1, usage); + if (result != nullptr) { + errno_t erc = strcpy_s(result, len + 1, source); + securec_check(erc, "\0", "\0"); + } + return result; +} + +bool SPIAutoConnect::Connect(Oid functionOid /* = InvalidOid */) +{ + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCtx = CurrentMemoryContext; + PG_TRY(); + { + if (!m_connected) { + knl_u_SPI_context* spi_cxt = &u_sess->SPI_cxt; + MOT_LOG_TRACE("Connecting to SPI: cur_id=%d, connected=%d", spi_cxt->_curid, spi_cxt->_connected); + if (functionOid == InvalidOid) { + m_rc = SPI_connect(); + } else { + m_rc = SPI_connect_ext(DestSPI, nullptr, nullptr, SPI_OPT_NONATOMIC, functionOid); + } + if ((m_rc == SPI_OK_CONNECT) || (m_rc == SPI_ERROR_CONNECT)) { + m_connected = true; + if (m_rc == SPI_OK_CONNECT) { + m_shouldDisconnect = true; + } + } + } + m_connectId = SPI_connectid(); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCtx); + HandleError(ErrorOp::CONNECT); + } + PG_END_TRY(); + return m_connected; +} + +void SPIAutoConnect::Disconnect() +{ + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCtx = CurrentMemoryContext; + PG_TRY(); + { + if (m_connected && m_shouldDisconnect) { + // it is possible that some error occurred and we are in intermediate state, so we first detect that, then + // cleanup previous connections, then cleanup our connection + knl_u_SPI_context* spi_cxt = &u_sess->SPI_cxt; + MOT_LOG_TRACE("Disconnecting from SPI: cur_id=%d, connected=%d", spi_cxt->_curid, spi_cxt->_connected); + if ((spi_cxt->_curid + 1) != spi_cxt->_connected) { // already disconnected, or invalid state + MOT_LOG_TRACE("SPI left in intermediate state"); + // first restore connection state + if (m_connectId >= 0) { + // cleanup leftover connections + MOT_LOG_TRACE("Cleaning up leftover connections up to %d", m_connectId + 1); + SPI_disconnect(m_connectId + 1); + // restore connection state + MOT_LOG_TRACE("Restoring connection state"); + SPI_restore_connection(); + MOT_LOG_TRACE( + "SPI connection state: cur_id=%d, connected=%d", spi_cxt->_curid, spi_cxt->_connected); + } + } + int rc = SPI_finish(); + if (rc == SPI_ERROR_UNCONNECTED) { + MOT_LOG_TRACE("Failed to disconnect from SPI - already disconnected"); + } else if (rc != SPI_OK_FINISH) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, + "JIT Compile", + "Failed to disconnect from SPI - %s (error code: %d)", + SPI_result_code_string(rc), + rc); + } + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCtx); + HandleError(ErrorOp::DISCONNECT); + } + PG_END_TRY(); + m_connected = false; + m_shouldDisconnect = false; +} + +void SPIAutoConnect::HandleError(ErrorOp errorOp) +{ + const char* operation = ErrorOpToString(errorOp); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while %s: %s", operation, edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while %s: %s", operation, edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); +} + +const char* SPIAutoConnect::ErrorOpToString(ErrorOp errorOp) +{ + switch (errorOp) { + case ErrorOp::CONNECT: + return "connecting to SPI"; + + case ErrorOp::DISCONNECT: + return "disconnecting from SPI"; + + default: + return ""; + } +} + +PGFunction GetPGFunctionInfo(Oid functionId, uint32_t* argCount, bool* isStrict /* = nullptr */) +{ + FmgrInfo flinfo; + fmgr_info(functionId, &flinfo); + MOT_LOG_TRACE("Retrieved function %u info: fn_addr=%p, fn_nargs=%d, fn_strict=%d, fn_retset=%d, fn_rettype=%u, " + "fn_rettypemod=%u, fnName=%s", + functionId, + flinfo.fn_addr, + (int)flinfo.fn_nargs, + (int)flinfo.fn_strict, + (int)flinfo.fn_retset, + flinfo.fn_rettype, + flinfo.fn_rettypemod, + flinfo.fnName); + *argCount = flinfo.fn_nargs; + if (isStrict != nullptr) { + *isStrict = flinfo.fn_strict; + } + return flinfo.fn_addr; +} + +PGFunction GetPGAggFunctionInfo(Oid functionId, uint32_t* argCount, bool* isStrict /* = nullptr */) +{ + MOT_LOG_TRACE("Searching for aggregation function %u", (unsigned)functionId); + HeapTuple aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(aggTuple)) { + MOT_LOG_TRACE("Failed to lookup aggregation function %u", (unsigned)functionId); + return nullptr; + } + + Form_pg_aggregate aggStruct = (Form_pg_aggregate)GETSTRUCT(aggTuple); + MOT_LOG_TRACE("Redirected to aggregation function %u (transfn: %u, finalfn: %u, directargs: %d)", + (unsigned)aggStruct->aggfnoid, + (unsigned)aggStruct->aggtransfn, + (unsigned)aggStruct->aggfinalfn, + aggStruct->aggnumdirectargs); + // we actually use the transition function for aggregation + PGFunction funcPtr = GetPGFunctionInfo(aggStruct->aggtransfn, argCount, isStrict); + ReleaseSysCache(aggTuple); + return funcPtr; +} + +bool GetPGAggTransFunctionInfo(Oid functionId, PGFunction* aggFunc, uint32_t* argCount, bool* isStrict /* = nullptr */) +{ + *aggFunc = nullptr; + MOT_LOG_TRACE("Searching for transition of aggregation function %u", (unsigned)functionId); + HeapTuple aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(aggTuple)) { + MOT_LOG_TRACE("Failed to lookup aggregation function %u", (unsigned)functionId); + return false; + } + + Form_pg_aggregate aggStruct = (Form_pg_aggregate)GETSTRUCT(aggTuple); + MOT_LOG_TRACE("Redirected to aggregation function %u (transfn: %u, finalfn: %u, directargs: %d)", + (unsigned)aggStruct->aggfnoid, + (unsigned)aggStruct->aggtransfn, + (unsigned)aggStruct->aggfinalfn, + aggStruct->aggnumdirectargs); + if (aggStruct->aggfinalfn != 0) { + *aggFunc = GetPGFunctionInfo(aggStruct->aggfinalfn, argCount, isStrict); + } + ReleaseSysCache(aggTuple); + return true; +} + +bool GetPGAggFinalFunctionInfo(Oid functionId, PGFunction* aggFunc, uint32_t* argCount, bool* isStrict /* = nullptr */) +{ + *aggFunc = nullptr; + MOT_LOG_TRACE("Searching for finalization of aggregation function %u", (unsigned)functionId); + HeapTuple aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(aggTuple)) { + MOT_LOG_TRACE("Failed to lookup aggregation function %u", (unsigned)functionId); + return false; + } + + Form_pg_aggregate aggStruct = (Form_pg_aggregate)GETSTRUCT(aggTuple); + MOT_LOG_TRACE("Redirected to aggregation function %u (transfn: %u, finalfn: %u, directargs: %d)", + (unsigned)aggStruct->aggfnoid, + (unsigned)aggStruct->aggtransfn, + (unsigned)aggStruct->aggfinalfn, + aggStruct->aggnumdirectargs); + if (aggStruct->aggfinalfn != 0) { + *aggFunc = GetPGFunctionInfo(aggStruct->aggfinalfn, argCount, isStrict); + } + ReleaseSysCache(aggTuple); + return true; +} + +bool ProcessCallSitePlan(JitCallSitePlan* callSitePlan, JitCallSite* callSite, PLpgSQL_function* function) +{ + // copy the context, it is valid and global + if (callSitePlan->m_queryPlan != nullptr) { + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(callSitePlan->m_expr, function, &attrs)) { + MOT_LOG_TRACE("Failed to get query attributes from expression: %s", callSitePlan->m_queryString); + return false; + } + MOT_LOG_TRACE("Generating code for called query: %s", callSitePlan->m_queryString); + if (MOT_CHECK_TRACE_LOG_LEVEL()) { + char* queryStr = nodeToString(attrs.m_query); + MOT_LOG_TRACE("Query tree: %s", queryStr); + pfree(queryStr); + } + callSite->m_queryContext = JitCodegenQuery( + attrs.m_query, callSitePlan->m_queryString, callSitePlan->m_queryPlan, JIT_CONTEXT_GLOBAL_SECONDARY); + if (callSite->m_queryContext == nullptr) { + MOT_LOG_TRACE("Failed to generate code for call-site plan: %s", callSite->m_queryString); + CleanupExprQueryAttrs(&attrs); + return false; + } + CleanupExprQueryAttrs(&attrs); + } else { + callSite->m_queryString = DupString(callSitePlan->m_queryString, JitContextUsage::JIT_CONTEXT_GLOBAL); + if (callSite->m_queryString == nullptr) { + MOT_LOG_TRACE("Failed to clone call site query string: %s", callSitePlan->m_queryString); + return false; + } + } + callSite->m_queryCmdType = callSitePlan->m_queryCmdType; + + // move the global tuple descriptor from the plan to the context + callSite->m_tupDesc = callSitePlan->m_tupDesc; + callSitePlan->m_tupDesc = nullptr; + + // clone parameter list from plan + int paramCount = callSitePlan->m_callParamCount; + if (paramCount > 0) { + size_t allocSize = sizeof(JitCallParamInfo) * paramCount; + JitCallParamInfo* result = (JitCallParamInfo*)MOT::MemGlobalAlloc(allocSize); + if (result == nullptr) { + MOT_LOG_TRACE( + "Failed to allocate %u bytes for parameter info in JIT function context", (unsigned)allocSize); + return false; + } + for (int j = 0; j < paramCount; ++j) { + result[j] = callSitePlan->m_callParamInfo[j]; + } + callSite->m_callParamInfo = result; + } else { + callSite->m_callParamInfo = nullptr; + } + callSite->m_callParamCount = paramCount; + callSite->m_exprIndex = callSitePlan->m_exprIndex; + callSite->m_isUnjittableInvoke = callSitePlan->m_isUnjittableInvoke; + callSite->m_isModStmt = callSitePlan->m_isModStmt; + callSite->m_isInto = callSitePlan->m_isInto; + return true; +} + +MotJitContext* ProcessInvokedPlan(JitFunctionPlan* functionPlan, JitContextUsage usage) +{ + SPIAutoConnect spiAutoConn; + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while generating code for function %s: %s (%u)", + functionPlan->_function_name, + SPI_result_code_string(rc), + rc); + return nullptr; + } + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile HeapTuple procTuple = nullptr; + volatile MotJitContext* jitContext = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionPlan->_function_id)); + if (!HeapTupleIsValid(procTuple)) { + MOT_LOG_TRACE("SP %s disqualified: Oid %u not found in pg_proc", + functionPlan->_function_name, + functionPlan->_function_id); + } else { + // now we try to generate JIT code for the function + MOT_LOG_TRACE("Generating code for invoked stored procedure: %s", functionPlan->_function_name); + jitContext = JitCodegenFunction(functionPlan->m_function, + procTuple, + functionPlan->_function_id, + nullptr, + (JitPlan*)functionPlan, + usage); + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while generating code for function %s: %s", functionPlan->_function_name, edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while generating code function %s: %s", + functionPlan->_function_name, + edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + if (procTuple != nullptr) { + ReleaseSysCache(procTuple); + } + return (MotJitContext*)jitContext; +} + +bool GetFuncTypeClass(JitFunctionPlan* functionPlan, TupleDesc* resultTupDesc, TypeFuncClass* typeClass) +{ + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile bool result = false; + volatile MemoryContext origCtx = CurrentMemoryContext; + FmgrInfo finfo = {}; + PG_TRY(); + { + fmgr_info(functionPlan->_function_id, &finfo); + + FunctionCallInfoData fcinfo; + InitFunctionCallInfoData(fcinfo, &finfo, functionPlan->m_paramCount, InvalidOid, nullptr, nullptr); + + *typeClass = get_call_result_type(&fcinfo, nullptr, resultTupDesc); + + FreeFunctionCallInfoData(fcinfo); + result = true; + } + PG_CATCH(); + { + // cleanup + (void)MemoryContextSwitchTo(origCtx); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while retrieving function %u type class: %s", functionPlan->_function_id, edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + return result; +} + +char* MakeInvokedQueryString(char* functionName, Oid functionId) +{ + char* result = nullptr; + MOT::mot_string qualifiedName; + if (!qualifiedName.format("%s.%u", functionName, functionId)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Generate Code", "Failed to format qualified invoked query string"); + } else { + result = DupString(qualifiedName.c_str(), JitContextUsage::JIT_CONTEXT_GLOBAL); + if (result == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate Code", + "Failed to duplicate qualified invoked query string: %s", + qualifiedName.c_str()); + } + } + return result; +} + +MemoryContext SwitchToSPICallerContext() +{ + MemoryContext oldCtx = nullptr; + if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { // SPI connected + if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmodule(MOD_MOT), + errmsg("SPI stack corrupted when copy tuple, connected level: %d", u_sess->SPI_cxt._connected))); + } + + MOT_LOG_TRACE("Switching to memory context of SPI caller (connect id = %d): %s --> %s", + u_sess->SPI_cxt._connected, + CurrentMemoryContext->name, + u_sess->SPI_cxt._current->savedcxt ? u_sess->SPI_cxt._current->savedcxt->name : "none"); + oldCtx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt); + } else { + MOT_LOG_WARN("Cannot switch to memory context of SPI caller: SPI is not connected (connected=%d, curid=%d), " + "switching to top memory context of current transaction instead", + u_sess->SPI_cxt._connected, + u_sess->SPI_cxt._curid); + oldCtx = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); + } + return oldCtx; +} + +Datum CopyDatum(Datum value, Oid type, bool isnull) +{ + if (isnull) { + return (Datum)0; + } + + if (JitExec::IsPrimitiveType(type)) { + return value; + } + + HeapTuple typeTuple = SearchSysCache1(TYPEOID, PointerGetDatum(type)); + Form_pg_type typeStruct = (Form_pg_type)GETSTRUCT(typeTuple); + bool typeByVal = typeStruct->typbyval; + int typeLen = typeStruct->typlen; + ReleaseSysCache(typeTuple); + Datum res = datumCopy(value, typeByVal, typeLen); + return res; +} } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_common.h b/src/gausskernel/storage/mot/jit_exec/jit_common.h index 920397f26..2f74fadb7 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_common.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_common.h @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * jit_common.h - * Definitions used both by LLVM and TVM jitted code. + * Definitions used by LLVM jitted code. * * IDENTIFICATION * src/gausskernel/storage/mot/jit_exec/jit_common.h @@ -26,23 +26,76 @@ #define JIT_COMMON_H #include "jit_helpers.h" -#include "jit_pgproc.h" #include "jit_context.h" #include "mot_engine.h" +#include "utils/plpgsql.h" +#include "funcapi.h" + /** @define The maximum number of constant objects that can be used in a query. */ #define MOT_JIT_MAX_CONST 1024 -// This file contains definitions used both by LLVM and TVM jitted code +/** @define Start profile region text. */ +#define MOT_JIT_PROFILE_BEGIN_MESSAGE "MOT JIT PROFILE BEGIN" + +/** @define End profile region text. */ +#define MOT_JIT_PROFILE_END_MESSAGE "MOT JIT PROFILE END" + +/*----------------------------- Common Fault Codes -----------------------------------*/ +/** @define JIT codes low bound. */ +#define JIT_FAULT_CODE_LOW 100 + +/** @define Internal error (probable software bug) during jitted function execution. */ +#define JIT_FAULT_INTERNAL_ERROR (JIT_FAULT_CODE_LOW + 0) + +/** @define Sub-transaction not closed fault. */ +#define JIT_FAULT_SUB_TX_NOT_CLOSED (JIT_FAULT_CODE_LOW + 1) + +/*----------------------------- Exception Oriign -----------------------------------*/ +/** @define Exception origin is internal. */ +#define JIT_EXCEPTION_INTERNAL 0 + +/** @define Exception origin is external. */ +#define JIT_EXCEPTION_EXTERNAL 1 + +// This file contains common definitions used by LLVM jitted code namespace JitExec { +using namespace dorado; + // forward declaration struct JitCompoundPlan; +struct JitCallSitePlan; +struct JitFunctionPlan; + +/** @struct JIT code generation statistics. */ +struct JitCodegenStats { + /** @var Total time taken to generate code. */ + uint64_t m_codegenTime; + + /** @var Time taken to verify the jitted function. */ + uint64_t m_verifyTime; + + /** @var Time taken to finalize the jitted function. */ + uint64_t m_finalizeTime; + + /** @var Time taken for the compilation (applicable only for LLVM). */ + uint64_t m_compileTime; +}; + +/** @brief Queries whether a fault code is a generic JIT fault code. */ +inline bool IsJitRuntimeFaultCode(int faultCode) +{ + return (faultCode >= JIT_FAULT_CODE_LOW); +} + +/** @brief Converts JIT generic fault code to string. */ +extern const char* JitRuntimeFaultToString(int faultCode); + +/** @brief Reset compile state for LLVM (cleanup in case of error). */ +extern void JitResetCompileState(); // common helpers for code generation -/** @brief Converts a PG command type to LLVM command type. */ -extern JitCommandType ConvertCommandType(int pgCommandType, bool isPKey); - /** @brief Converts an LLVM command type to string form. */ extern const char* CommandToString(JitCommandType commandType); @@ -52,6 +105,15 @@ extern bool IsRangeCommand(JitCommandType commandType); /** @breif Queries whether an LLVM command requires a JOIN scan. */ extern bool IsJoinCommand(JitCommandType commandType); +/** @breif Queries whether a JIT command is a SELECT command (includes JOIN). */ +extern bool IsSelectCommand(JitCommandType commandType); + +/** @breif Queries whether an LLVM command uses index objects. */ +extern bool IsCommandUsingIndex(JitCommandType commandType); + +/** @breif Queries whether a JIT command is a write operation command. */ +extern bool IsWriteCommand(JitCommandType commandType); + /** @brief Extract table from query. */ extern MOT::Table* GetTableFromQuery(const Query* query); @@ -85,12 +147,52 @@ extern bool IsFuncIdSupported(int funcId); /** @brief Queries whether the WHERE clause represents a full-prefix search. */ extern bool IsFullPrefixSearch(const int* columnArray, int columnCount, int* firstZeroColumn); +/** @brief Classifies operator sorting. */ +extern JitQuerySortOrder ClassifyOperatorSortOrder(Oid op); + /** @brief Retrieves the sort order of a query if specified. */ extern JitQuerySortOrder GetQuerySortOrder(const Query* query); /** @brief Retrieves the type of the aggregate column. */ extern int GetAggregateTupleColumnIdAndType(const Query* query, int& zeroType); +inline void* JitMemAlloc(uint64_t size, JitContextUsage usage) +{ + if (IsJitContextUsageGlobal(usage)) { + return MOT::MemGlobalAlloc(size); + } else { + return MOT::MemSessionAlloc(size); + } +} + +inline void JitMemFree(void* object, JitContextUsage usage) +{ + MOT_ASSERT(object != nullptr); + if (IsJitContextUsageGlobal(usage)) { + MOT::MemGlobalFree(object); + } else { + MOT::MemSessionFree(object); + } +} + +inline void* JitMemRealloc(void* object, uint64_t newSize, MOT::MemReallocFlags flags, JitContextUsage usage) +{ + if (IsJitContextUsageGlobal(usage)) { + return MOT::MemGlobalRealloc(object, newSize, flags); + } else { + return MOT::MemSessionRealloc(object, newSize, flags); + } +} + +inline void* JitMemAllocAligned(uint64_t size, uint32_t alignment, JitContextUsage usage) +{ + if (IsJitContextUsageGlobal(usage)) { + return MOT::MemGlobalAllocAligned(size, alignment); + } else { + return MOT::MemSessionAllocAligned(size, alignment); + } +} + /** @struct Compile-time table information. */ struct TableInfo { /** @var The table used for the query. */ @@ -125,7 +227,7 @@ extern bool InitTableInfo(TableInfo* table_info, MOT::Table* table, MOT::Index* * @brief Cleanup table information. * @param table_info The table information structure to cleanup. */ -extern void DestroyTableInfo(TableInfo* table_info); +extern void DestroyTableInfo(TableInfo* tableInfo); /** * @brief Prepares sub-query data items for a JIT context (table, index and tuple descriptor). @@ -133,13 +235,190 @@ extern void DestroyTableInfo(TableInfo* table_info); * @param plan The compound plan for the query. * @return True if operations succeeded, otherwise false. */ -extern bool PrepareSubQueryData(JitContext* jitContext, JitCompoundPlan* plan); +extern bool PrepareSubQueryData(JitQueryContext* jitContext, JitCompoundPlan* plan); + +/** @brief Extracts the function name from a parse tree of function invocation command. */ +extern const char* ExtractFunctionName(Node* parseTree); /** @brief Prepares array of global datum objects from array of constants. */ extern bool PrepareDatumArray(Const* constArray, uint32_t constCount, JitDatumArray* datumArray); /** @brief Clones an interval datum into global memory. */ extern bool CloneDatum(Datum source, int type, Datum* target, JitContextUsage usage); + +/** @brief Prints a datum to log. */ +extern void PrintDatum(MOT::LogLevel logLevel, Oid ptype, Datum datum, bool isnull); + +/** @brief Convert numeric Datum to double precision value. */ +extern double NumericToDouble(Datum numeric_value); + +/** @struct Attributes of a parsed query in a PG/PLSQL expression. */ +struct ExprQueryAttrs { + /** @brief The SPI plan for the query. */ + SPIPlanPtr m_spiPlan; + + /** @brief The cached plan source for the query. */ + CachedPlanSource* m_planSource; + + /** @brief The parsed query tree. */ + Query* m_query; + + /** @brief Bitmap for specifying which parameter of the ones used to parse the query are actually used. */ + Bitmapset* m_paramNos; +}; + +/** @brief Prepares fake execution state for parsing. */ +extern void PrepareExecState(PLpgSQL_execstate* estate, PLpgSQL_function* function); + +/** @brief Extract plan and query from expression (requires fake execution state. */ +extern bool GetExprQueryAttrs(PLpgSQL_expr* expr, PLpgSQL_function* function, ExprQueryAttrs* attrs); + +/** @brief Releases the resources associated with expression query attributes. */ +extern void CleanupExprQueryAttrs(ExprQueryAttrs* attrs); + +/** @brief Creates a parameter array with the given amount of parameter entries. */ +extern ParamListInfo CreateParamListInfo(int paramCount, bool isGlobalUsage); + +/** @brief Retrieves Compiled stored procedure by id. */ +extern PLpgSQL_function* GetPGCompiledFunction(Oid functionOid, const char* funcName = nullptr); + +/** @convert SQL value to string. */ +extern void SqlStateToCode(int sqlState, char* sqlStateCode); + +extern void RaiseEreport(int sqlState, const char* errorMessage, const char* errorDetail, const char* errorHint); + +/** @brief Duplicates a string. */ +extern char* DupString(const char* source, JitContextUsage usage); + +/** @class Utility class for managing a short-lived SPI connection. */ +class SPIAutoConnect { +public: + explicit SPIAutoConnect(bool deferConnect = false, Oid functionOid = InvalidOid) + : m_rc(0), m_connected(false), m_connectId(-1), m_shouldDisconnect(false) + { + if (!deferConnect) { + (void)Connect(functionOid); + } + } + + ~SPIAutoConnect() + { + Disconnect(); + } + + bool Connect(Oid functionOid = InvalidOid); + + inline bool IsConnected() const + { + return m_connected; + } + + inline int GetErrorCode() const + { + return m_rc; + } + + void Disconnect(); + +private: + int m_rc; + bool m_connected; + int m_connectId; + bool m_shouldDisconnect; + enum class ErrorOp { CONNECT, DISCONNECT }; + + void HandleError(ErrorOp errorOp); + const char* ErrorOpToString(ErrorOp errorOp); +}; + +/** @struct PG function information. */ +struct PGFunctionInfo { + /** @var The function identifier. */ + Oid m_functionId; + + /** @var The function pointer. */ + PGFunction m_funciton; + + /** @var The number of arguments required by the function. */ + uint32_t m_args; +}; + +/** + * @brief Retrieves PG function information. + * @param functionId The function identifier. + * @param[out] argCount The number of arguments used by the function. + * @param[out, opt] Specifies whether the function is strict. + * @return The function, or null if none was found. + */ +extern PGFunction GetPGFunctionInfo(Oid functionId, uint32_t* argCount, bool* isStrict = nullptr); + +/** + * @brief Retrieves PG aggregation function information. + * @param functionId The function identifier. + * @param[out] argCount The number of arguments used by the function. + * @param[out, opt] isStrict Specifies whether the function is strict. + * @return The aggregate function, or null if none was found. + */ +extern PGFunction GetPGAggFunctionInfo(Oid functionId, uint32_t* argCount, bool* isStrict = nullptr); + +/** + * @brief Retrieves PG aggregation transition function information. + * @param functionId The function identifier. + * @param[out] aggFunc The aggregation transition function (could be null, which is OK, check result in this case). + * @param[out] argCount The number of arguments used by the function. + * @param[out, opt] isStrict Specifies whether the function is strict. + * @return True if operation succeeded, otherwise false. + * @note It is possible that no transition function is defined for this aggregate. In this case the return value is + * true, and the @ref aggFunc output parameter is set to null. In case of failure, @ref aggFunc is set to null, and + * false is returned. + */ +extern bool GetPGAggTransFunctionInfo( + Oid functionId, PGFunction* aggFunc, uint32_t* argCount, bool* isStrict = nullptr); + +/** + * @brief Retrieves PG aggregation finalization function information. + * @param functionId The function identifier. + * @param[out] aggFunc The aggregation finalization function (could be null, which is OK, check result in this case). + * @param[out] argCount The number of arguments used by the function. + * @param[out, opt] isStrict Specifies whether the function is strict. + * @return True if operation succeeded, otherwise false. + * @note It is possible that no finalization function is defined for this aggregate. In this case the return value is + * true, and the @ref aggFunc output parameter is set to null. In case of failure, @ref aggFunc is set to null, and + * false is returned. + */ +extern bool GetPGAggFinalFunctionInfo( + Oid functionId, PGFunction* aggFunc, uint32_t* argCount, bool* isStrict = nullptr); + +/** @brief Generates code from a call site plan. */ +extern bool ProcessCallSitePlan(JitCallSitePlan* callSitePlan, JitCallSite* callSite, PLpgSQL_function* function); + +/** @brief Generates code for an invoked stored procedure. */ +extern MotJitContext* ProcessInvokedPlan(JitFunctionPlan* functionPlan, JitContextUsage usage); + +/** @brief Common helper in code-generation. */ +extern bool GetFuncTypeClass(JitFunctionPlan* functionPlan, TupleDesc* resultTupDesc, TypeFuncClass* typeClass); + +/** @brief Prepare invoked stored procedure query string. */ +extern char* MakeInvokedQueryString(char* functionName, Oid functionId); + +/** @brief Switch memory context to caller's. */ +extern MemoryContext SwitchToSPICallerContext(); + +/** @brief Make datum copy. */ +extern Datum CopyDatum(Datum value, Oid type, bool isnull); } // namespace JitExec +#ifdef MOT_JIT_DEBUG +#define JIT_PRINT_DATUM(logLevel, msg, ptype, datum, isnull) \ + if (MOT_CHECK_LOG_LEVEL(logLevel)) { \ + MOT_LOG_BEGIN(logLevel, "%s: ", msg); \ + JitExec::PrintDatum(logLevel, ptype, datum, isnull); \ + MOT_LOG_END(logLevel); \ + } +#define DEBUG_PRINT_DATUM(msg, ptype, datum, isnull) JIT_PRINT_DATUM(MOT::LogLevel::LL_DEBUG, msg, ptype, datum, isnull) +#else +#define JIT_PRINT_DATUM(logLevel, msg, ptype, datum, isnull) +#define DEBUG_PRINT_DATUM(msg, ptype, datum, isnull) +#endif + #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_context.cpp b/src/gausskernel/storage/mot/jit_exec/jit_context.cpp index da0a868d8..8e5b54592 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_context.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_context.cpp @@ -32,28 +32,74 @@ #include "postgres.h" #include "knl/knl_session.h" #include "storage/ipc.h" +#include "executor/executor.h" + #include "global.h" #include "jit_context.h" #include "jit_context_pool.h" #include "jit_common.h" -#include "jit_tvm.h" #include "mot_internal.h" #include "jit_source.h" #include "mm_global_api.h" +#include "mot_atomic_ops.h" +#include "jit_plan_sp.h" +#include "debug_utils.h" +#include "jit_source_map.h" +#include "jit_statistics.h" namespace JitExec { -DECLARE_LOGGER(JitContext, JitExec); +DECLARE_LOGGER(MotJitContext, JitExec); // The global JIT context pool static JitContextPool g_globalJitCtxPool __attribute__((aligned(64))) = {0}; // forward declarations static JitContextPool* AllocSessionJitContextPool(); -static MOT::Key* PrepareJitSearchKey(JitContext* jitContext, MOT::Index* index); -static void CleanupJitContextPrimary(JitContext* jitContext); -static void CleanupJitContextInner(JitContext* jitContext); -static void CleanupJitContextSubQueryDataArray(JitContext* jitContext); -static void CleanupJitContextSubQueryData(JitContext::SubQueryData* subQueryData); + +static bool AllocJitQueryExecState(JitQueryContext* jitContext); +static bool PrepareJitQueryContext(JitQueryContext* jitContext); +static bool PrepareMainSearchKey(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareUpdateBitmap(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareEndIteratorKey(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareInnerSrearchKey(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareInnerEndIteratorKey(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareCompoundSubQuery(JitQueryContext* jitContext, JitQueryExecState* execState); +static bool PrepareInvokeContext(JitQueryContext* jitContext, JitQueryExecState* execState); + +static bool AllocJitFunctionExecState(JitFunctionContext* jitContext); +static bool PrepareJitFunctionContext(JitFunctionContext* jitContext); +static bool PrepareCallSite( + JitFunctionContext* jitContext, JitInvokedQueryExecState* execState, JitCallSite* callSite, int subQueryId); + +static void DestroyJitQueryContext(JitQueryContext* jitContext, bool isDropCachedPlan = false); +static void DestroyJitFunctionContext(JitFunctionContext* jitContext, bool isDropCachedPlan = false); +static MOT::Key* PrepareJitSearchKey(MotJitContext* jitContext, MOT::Index* index); +static bool CloneTupleDesc(TupleDesc source, TupleDesc* target, JitContextUsage usage); +static bool CloneJitQueryContext(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage); +static bool CloneJitFunctionContext(JitFunctionContext* source, JitFunctionContext* target, JitContextUsage usage); +static void CleanupJitContextPrimary(JitQueryContext* jitContext, bool isDropCachedPlan = false); +static void CleanupJitContextInner(JitQueryContext* jitContext, bool isDropCachedPlan = false); +static void CleanupJitSubQueryContextArray(JitQueryContext* jitContext, bool isDropCachedPlan = false); +static void CleanupJitSubQueryContext( + JitSubQueryContext* subQueryContext, JitSubQueryExecState* subQueryExecState, bool isDropCachedPlan = false); +static bool JitQueryContextRefersRelation(JitQueryContext* jitContext, uint64_t relationId); +static bool JitFunctionContextRefersRelation(JitFunctionContext* jitContext, uint64_t relationId); +static void PurgeJitQueryContext(JitQueryContext* jitContext, uint64_t relationId); +static void PurgeJitFunctionContext(JitFunctionContext* jitContext, uint64_t relationId); + +static bool ReplenishDatumArray(JitDatumArray* source, JitDatumArray* target, JitContextUsage usage, int depth); +static bool ReplenishInvokeParams(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth); +static bool ReplenishSubQueryArray(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth); +static bool ReplenishAggregateArray(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth); +static bool ReplenishJitQueryContext(JitQueryContext* source, JitQueryContext* target, int depth); +static bool ReplenishParamListInfo(JitCallSite* target, JitCallSite* source, JitContextUsage usage, int depth); +static bool ReplenishJitFunctionContext(JitFunctionContext* source, JitFunctionContext* target, int depth); +static bool ReplenishJitContext(MotJitContext* source, MotJitContext* target, int depth); +static bool RevalidateJitQueryContext(MotJitContext* jitContext); +static bool RevalidateJitFunctionContext(MotJitContext* jitContext); +static void DestroyJitNonNativeSortExecState( + JitNonNativeSortExecState* jitNonNativeSortExecState, JitContextUsage usage); +static void PropagateValidState(MotJitContext* jitContext, uint8_t invalidFlag, uint64_t relationId); extern bool InitGlobalJitContextPool() { @@ -65,12 +111,15 @@ extern void DestroyGlobalJitContextPool() DestroyJitContextPool(&g_globalJitCtxPool); } -extern JitContext* AllocJitContext(JitContextUsage usage) +extern MotJitContext* AllocJitContext(JitContextUsage usage, JitContextType type) { - JitContext* result = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { + MotJitContext* result = nullptr; + if (IsJitContextUsageGlobal(usage)) { // allocate from global pool result = AllocPooledJitContext(&g_globalJitCtxPool); + if (result != nullptr) { + JitStatisticsProvider::GetInstance().AddGlobalBytes((int64_t)GetJitContextSize()); + } } else { // allocate from session local pool (create pool on demand and schedule cleanup during end of session) if (u_sess->mot_cxt.jit_session_context_pool == nullptr) { @@ -80,19 +129,47 @@ extern JitContext* AllocJitContext(JitContextUsage usage) } } result = AllocPooledJitContext(u_sess->mot_cxt.jit_session_context_pool); + if (result != nullptr) { + result->m_sessionId = MOT_GET_CURRENT_SESSION_ID(); + JitStatisticsProvider::GetInstance().AddSessionBytes((int64_t)GetJitContextSize()); + ++u_sess->mot_cxt.jit_context_count; + MOT_LOG_TRACE("AllocJitContext(): Current session (%u) JIT context count: %u", + MOT_GET_CURRENT_SESSION_ID(), + u_sess->mot_cxt.jit_context_count); + } + } + + if (result != nullptr) { + result->m_usage = usage; + result->m_contextType = type; + MOT_LOG_TRACE("Allocated %s JIT %s context %p [%u:%u:%u]", + JitContextUsageToString(result->m_usage), + JitContextTypeToString(result->m_contextType), + result, + result->m_poolId, + result->m_subPoolId, + result->m_contextId); + } else { + MOT_LOG_ERROR( + "Failed to allocate %s JIT %s context", JitContextUsageToString(usage), JitContextTypeToString(type)); } return result; } -extern void FreeJitContext(JitContext* jitContext) +extern void FreeJitContext(MotJitContext* jitContext) { if (jitContext != nullptr) { - if (jitContext->m_usage == JIT_CONTEXT_GLOBAL) { + if (IsJitContextUsageGlobal(jitContext->m_usage)) { FreePooledJitContext(&g_globalJitCtxPool, jitContext); + JitStatisticsProvider::GetInstance().AddGlobalBytes(-((int64_t)GetJitContextSize())); } else { // in this scenario it is always called by the session who created the context --u_sess->mot_cxt.jit_context_count; + MOT_LOG_TRACE("FreeJitContext(): Current session (%u) JIT context count: %u", + MOT_GET_CURRENT_SESSION_ID(), + u_sess->mot_cxt.jit_context_count); FreePooledJitContext(u_sess->mot_cxt.jit_session_context_pool, jitContext); + JitStatisticsProvider::GetInstance().AddSessionBytes(-((int64_t)GetJitContextSize())); } } } @@ -107,12 +184,7 @@ static bool CloneDatumArray(JitDatumArray* source, JitDatumArray* target, JitCon } size_t allocSize = sizeof(JitDatum) * datumCount; - JitDatum* datumArray = nullptr; - if (usage == JIT_CONTEXT_GLOBAL) { - datumArray = (JitDatum*)MOT::MemGlobalAlloc(allocSize); - } else { - datumArray = (JitDatum*)MOT::MemSessionAlloc(allocSize); - } + JitDatum* datumArray = (JitDatum*)JitMemAlloc(allocSize, usage); if (datumArray == nullptr) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum array", (unsigned)allocSize); @@ -131,18 +203,10 @@ static bool CloneDatumArray(JitDatumArray* source, JitDatumArray* target, JitCon MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", "Failed to clone datum array entry"); for (uint32_t j = 0; j < i; ++j) { if (!IsPrimitiveType(datumArray[j].m_type)) { - if (usage == JIT_CONTEXT_GLOBAL) { - MOT::MemGlobalFree(DatumGetPointer(datumArray[j].m_datum)); - } else { - MOT::MemGlobalFree(DatumGetPointer(datumArray[j].m_datum)); - } + JitMemFree(DatumGetPointer(datumArray[j].m_datum), usage); } } - if (usage == JIT_CONTEXT_GLOBAL) { - MOT::MemGlobalFree(datumArray); - } else { - MOT::MemSessionFree(datumArray); - } + JitMemFree(datumArray, usage); return false; } } @@ -154,66 +218,490 @@ static bool CloneDatumArray(JitDatumArray* source, JitDatumArray* target, JitCon return true; } -extern JitContext* CloneJitContext(JitContext* sourceJitContext) +extern MotJitContext* CloneJitContext(MotJitContext* source, JitContextUsage usage) { - MOT_LOG_TRACE("Cloning JIT context %p of query: %s", sourceJitContext, sourceJitContext->m_queryString); - JitContext* result = AllocJitContext(JIT_CONTEXT_LOCAL); // clone is always for local use - if (result == nullptr) { + const char* itemName = (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) ? "query" : "function"; + MOT_LOG_TRACE("Cloning %s JIT context %p (valid state %x) of %s: %s", + JitContextUsageToString(source->m_usage), + source, + MOT_ATOMIC_LOAD(source->m_validState), + itemName, + source->m_queryString); + MotJitContext* target = AllocJitContext(usage, source->m_contextType); + if (target == nullptr) { MOT_LOG_TRACE("Failed to allocate JIT context object"); return nullptr; } - result->m_llvmFunction = sourceJitContext->m_llvmFunction; - result->m_tvmFunction = sourceJitContext->m_tvmFunction; - result->m_commandType = sourceJitContext->m_commandType; - if (!CloneDatumArray(&sourceJitContext->m_constDatums, &result->m_constDatums, JIT_CONTEXT_LOCAL)) { + // no need to clone module/code-gen object (they are safe in the source context) + target->m_llvmFunction = source->m_llvmFunction; + target->m_llvmSPFunction = source->m_llvmSPFunction; + target->m_commandType = source->m_commandType; + target->m_validState = source->m_validState; + target->m_queryString = source->m_queryString; + AddJitSourceContext(source->m_jitSource, target); // register target for cleanup due to DDL + if (!CloneDatumArray(&source->m_constDatums, &target->m_constDatums, usage)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", "Failed to clone constant datum array"); - DestroyJitContext(result); + DestroyJitContext(target); return nullptr; } - result->m_table = sourceJitContext->m_table; - result->m_index = sourceJitContext->m_index; - result->m_indexId = sourceJitContext->m_indexId; - result->m_argCount = sourceJitContext->m_argCount; - result->m_nullColumnId = sourceJitContext->m_nullColumnId; - result->m_queryString = sourceJitContext->m_queryString; - result->m_innerTable = sourceJitContext->m_innerTable; - result->m_innerIndex = sourceJitContext->m_innerIndex; - result->m_innerIndexId = sourceJitContext->m_innerIndexId; - result->m_subQueryCount = sourceJitContext->m_subQueryCount; + + bool result = true; + if (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = CloneJitQueryContext((JitQueryContext*)source, (JitQueryContext*)target, usage); + } else if (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + result = CloneJitFunctionContext((JitFunctionContext*)source, (JitFunctionContext*)target, usage); + } + + if (!result) { + MOT_LOG_TRACE("Failed to clone %s JIT context %p: %s", + JitContextUsageToString(source->m_usage), + source, + itemName, + source->m_queryString); + DestroyJitContext(target); + target = nullptr; + } else { + if (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + MOT_LOG_TRACE("Cloned %s query JIT context %p into %s JIT context %p (table=%p): %s", + JitContextUsageToString(source->m_usage), + source, + JitContextUsageToString(usage), + target, + ((JitQueryContext*)target)->m_table, + source->m_queryString); + } else { + MOT_LOG_TRACE("Cloned %s function JIT context %p into %s JIT context %p (procid=%u): %s", + JitContextUsageToString(source->m_usage), + source, + JitContextUsageToString(usage), + target, + ((JitFunctionContext*)target)->m_functionOid, + source->m_queryString); + } + } + + return target; +} + +static void DestroyJitNonNativeSortParamsMembers(JitNonNativeSortParams* sortParams, JitContextUsage usage) +{ + if (sortParams->sortColIdx) { + JitMemFree(sortParams->sortColIdx, usage); + sortParams->sortColIdx = nullptr; + } + if (sortParams->sortOperators) { + JitMemFree(sortParams->sortOperators, usage); + sortParams->sortOperators = nullptr; + } + if (sortParams->collations) { + JitMemFree(sortParams->collations, usage); + sortParams->collations = nullptr; + } + if (sortParams->nullsFirst) { + JitMemFree(sortParams->nullsFirst, usage); + sortParams->nullsFirst = nullptr; + } +} + +extern void DestroyJitNonNativeSortParams(JitNonNativeSortParams* sortParams, JitContextUsage usage) +{ + if (sortParams) { + DestroyJitNonNativeSortParamsMembers(sortParams, usage); + JitMemFree(sortParams, usage); + } +} + +static bool AllocJitNonNativeSortParamsMembers(JitNonNativeSortParams* sortParams, int numCols, JitContextUsage usage) +{ + if (numCols < 1) { + MOT_LOG_ERROR("Cannot create JitNonNativeSortParams if numCols is %d", numCols); + return false; + } + + sortParams->numCols = numCols; + uint32_t totalAllocSize = numCols * sizeof(AttrNumber) + 2 * numCols * sizeof(Oid) + numCols * sizeof(bool); + + sortParams->sortColIdx = (AttrNumber*)JitMemAlloc(numCols * sizeof(AttrNumber), usage); + sortParams->sortOperators = (Oid*)JitMemAlloc(numCols * sizeof(Oid), usage); + sortParams->collations = (Oid*)JitMemAlloc(numCols * sizeof(Oid), usage); + sortParams->nullsFirst = (bool*)JitMemAlloc(numCols * sizeof(bool), usage); + if (sortParams->sortColIdx == nullptr || sortParams->sortOperators == nullptr || + sortParams->collations == nullptr || sortParams->nullsFirst == nullptr) { + MOT_LOG_TRACE("Generate JIT Code", + "AllocJitNonNativeSortParamsMembers(): Failed to allocate memory for JitNonNativeSortParams members. " + "total alloc size = %u, numCols = d%, sortColIdx = %p, sortOperators = %p, collations = %p, nullsFirst = " + "%p", + totalAllocSize, + numCols, + sortParams->sortColIdx, + sortParams->sortOperators, + sortParams->collations, + sortParams->nullsFirst); + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate memory of size %u for Non-native sort params.", + totalAllocSize); + DestroyJitNonNativeSortParamsMembers(sortParams, usage); + return false; + } + + errno_t erc; + erc = memset_s(sortParams->sortColIdx, numCols * sizeof(AttrNumber), 0, numCols * sizeof(AttrNumber)); + securec_check(erc, "\0", "\0"); + erc = memset_s(sortParams->sortOperators, numCols * sizeof(Oid), 0, numCols * sizeof(Oid)); + securec_check(erc, "\0", "\0"); + erc = memset_s(sortParams->collations, numCols * sizeof(Oid), 0, numCols * sizeof(Oid)); + securec_check(erc, "\0", "\0"); + erc = memset_s(sortParams->nullsFirst, numCols * sizeof(bool), 0, numCols * sizeof(bool)); + securec_check(erc, "\0", "\0"); + + return true; +} + +extern JitNonNativeSortParams* AllocJitNonNativeSortParams(int numCols, JitContextUsage usage) +{ + uint64_t allocSize = sizeof(struct JitNonNativeSortParams); + JitNonNativeSortParams* jitNonNativeSortParams = (JitNonNativeSortParams*)JitMemAlloc(allocSize, usage); + if (jitNonNativeSortParams == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for JitNonNativeSortParams", + (unsigned)allocSize); + return nullptr; + } + + errno_t erc = memset_s( + jitNonNativeSortParams, sizeof(struct JitNonNativeSortParams), 0, sizeof(struct JitNonNativeSortParams)); + securec_check(erc, "\0", "\0"); + + if (!AllocJitNonNativeSortParamsMembers(jitNonNativeSortParams, numCols, usage)) { + MOT_LOG_ERROR("Failed to allocate JitNonNativeSortParams members. numcols: %d", numCols); + DestroyJitNonNativeSortParams(jitNonNativeSortParams, usage); + jitNonNativeSortParams = nullptr; + } + + return jitNonNativeSortParams; +} + +// Assume all memory in dest object was already allocated +bool CopyJitNonNativeSortParams(JitNonNativeSortParams* src, JitNonNativeSortParams* dest) +{ + MOT_ASSERT(src->numCols == dest->numCols); + + if (src->numCols != dest->numCols) { + MOT_LOG_ERROR("Cannot copy Non native sort params. src numCols (%d) is not " + "equal to dest numCols (%d)", + src->numCols, + dest->numCols); + + return false; + } + + int numCols = src->numCols; + + // Copy data + errno_t erc; + erc = memcpy_s(dest->sortColIdx, numCols * sizeof(AttrNumber), src->sortColIdx, numCols * sizeof(AttrNumber)); + securec_check(erc, "\0", "\0"); + + erc = memcpy_s(dest->sortOperators, numCols * sizeof(Oid), src->sortOperators, numCols * sizeof(Oid)); + securec_check(erc, "\0", "\0"); + + erc = memcpy_s(dest->collations, numCols * sizeof(Oid), src->collations, numCols * sizeof(Oid)); + securec_check(erc, "\0", "\0"); + + erc = memcpy_s(dest->nullsFirst, numCols * sizeof(bool), src->nullsFirst, numCols * sizeof(bool)); + securec_check(erc, "\0", "\0"); + + dest->numCols = src->numCols; + dest->plan_node_id = src->plan_node_id; + dest->bound = src->bound; + dest->scanDir = src->scanDir; + + return true; +} + +extern JitNonNativeSortParams* CloneJitNonNativeSortParams(JitNonNativeSortParams* src, JitContextUsage usage) +{ + JitNonNativeSortParams* jitNonNativeSortParams = AllocJitNonNativeSortParams(src->numCols, usage); + + if (jitNonNativeSortParams == nullptr) { + MOT_LOG_ERROR("Failed to allocate JitNonNativeSortParams with %d columns", src->numCols); + + return nullptr; + } + + if (!CopyJitNonNativeSortParams(src, jitNonNativeSortParams)) { + MOT_LOG_ERROR("Failed to copy non native params with %d columns", src->numCols); + + DestroyJitNonNativeSortParams(jitNonNativeSortParams, usage); + jitNonNativeSortParams = nullptr; + + return nullptr; + } + + return jitNonNativeSortParams; +} + +static inline void JitQueryContextSetTablesAndIndices(JitQueryContext* source, JitQueryContext* target) +{ + target->m_table = source->m_table; + target->m_tableId = source->m_tableId; + target->m_index = source->m_index; + target->m_indexId = source->m_indexId; + target->m_innerTable = source->m_innerTable; + target->m_innerTableId = source->m_innerTableId; + target->m_innerIndex = source->m_innerIndex; + target->m_innerIndexId = source->m_innerIndexId; +} + +static bool CloneJitQueryContext(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage) +{ + JitQueryContextSetTablesAndIndices(source, target); + target->m_aggCount = source->m_aggCount; + target->m_subQueryCount = source->m_subQueryCount; + target->m_invokeParamCount = source->m_invokeParamCount; + target->m_subQueryContext = nullptr; + target->m_invokeContext = nullptr; + + if (source->m_invokeContext != nullptr) { + MOT_LOG_TRACE("Cloning invoked stored procedure context"); + target->m_invokeContext = (JitFunctionContext*)CloneJitContext(source->m_invokeContext, usage); + if (target->m_invokeContext == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone invocation context"); + return false; + } + target->m_invokeContext->m_parentContext = target; + } + if (source->m_invokeParamCount > 0) { + size_t allocSize = sizeof(JitParamInfo) * source->m_invokeParamCount; + target->m_invokeParamInfo = (JitParamInfo*)JitMemAlloc(allocSize, usage); + if (target->m_invokeParamInfo == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u invoke parameter mode array items in JIT context object", + (unsigned)allocSize, + (unsigned)source->m_invokeParamCount); + return false; + } + errno_t erc = memcpy_s(target->m_invokeParamInfo, allocSize, source->m_invokeParamInfo, allocSize); + securec_check(erc, "\0", "\0"); + } else { + target->m_invokeParamInfo = nullptr; + } + + MOT_ASSERT((target->m_invokeParamCount == 0 && target->m_invokeParamInfo == nullptr) || + (target->m_invokeParamCount > 0 && target->m_invokeParamInfo != nullptr)); + + if (source->m_invokedQueryString != nullptr) { + target->m_invokedQueryString = DupString(source->m_invokedQueryString, usage); + if (target->m_invokedQueryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to duplicate invoke query string: %s", + source->m_invokedQueryString); + return false; + } + } + target->m_invokedFunctionOid = source->m_invokedFunctionOid; + target->m_invokedFunctionTxnId = source->m_invokedFunctionTxnId; // clone sub-query tuple descriptor array - MOT_LOG_TRACE("Cloning %u sub-query data items", (unsigned)sourceJitContext->m_subQueryCount); - if (sourceJitContext->m_subQueryCount > 0) { - uint32_t allocSize = sizeof(JitContext::SubQueryData) * sourceJitContext->m_subQueryCount; - result->m_subQueryData = (JitContext::SubQueryData*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); - if (result->m_subQueryData == nullptr) { + if (target->m_subQueryCount > 0) { + MOT_LOG_TRACE("Cloning %u sub-query data items", (unsigned)source->m_subQueryCount); + size_t allocSize = sizeof(JitSubQueryContext) * source->m_subQueryCount; + target->m_subQueryContext = (JitSubQueryContext*)JitMemAllocAligned(allocSize, L1_CACHE_LINE, usage); + if (target->m_subQueryContext == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to allocate %u bytes for %u sub-query data array in JIT context object", - allocSize, - (unsigned)sourceJitContext->m_subQueryCount); - FreeJitContext(result); - return nullptr; + (unsigned)allocSize, + (unsigned)source->m_subQueryCount); + return false; } - for (uint32_t i = 0; i < sourceJitContext->m_subQueryCount; ++i) { - // copy known members - result->m_subQueryData[i].m_commandType = sourceJitContext->m_subQueryData[i].m_commandType; - result->m_subQueryData[i].m_table = sourceJitContext->m_subQueryData[i].m_table; - result->m_subQueryData[i].m_index = sourceJitContext->m_subQueryData[i].m_index; - result->m_subQueryData[i].m_indexId = sourceJitContext->m_subQueryData[i].m_indexId; - - // nullify other members - result->m_subQueryData[i].m_tupleDesc = nullptr; - result->m_subQueryData[i].m_slot = nullptr; - result->m_subQueryData[i].m_searchKey = nullptr; - result->m_subQueryData[i].m_endIteratorKey = nullptr; + for (uint32_t i = 0; i < source->m_subQueryCount; ++i) { + target->m_subQueryContext[i].m_commandType = source->m_subQueryContext[i].m_commandType; + target->m_subQueryContext[i].m_table = source->m_subQueryContext[i].m_table; + target->m_subQueryContext[i].m_tableId = source->m_subQueryContext[i].m_tableId; + target->m_subQueryContext[i].m_index = source->m_subQueryContext[i].m_index; + target->m_subQueryContext[i].m_indexId = source->m_subQueryContext[i].m_indexId; } } - MOT_LOG_TRACE("Cloned JIT context %p into %p (table=%p)", sourceJitContext, result, result->m_table); - return result; + if (source->m_nonNativeSortParams) { + target->m_nonNativeSortParams = CloneJitNonNativeSortParams(source->m_nonNativeSortParams, usage); + if (target->m_nonNativeSortParams == nullptr) { + MOT_LOG_ERROR("Failed to clone JitNonNativeSortParams object with %d columns", + source->m_nonNativeSortParams->numCols); + return false; + } + } + + MOT_LOG_TRACE("Cloned JIT context %p into %p (table=%p)", source, target, target->m_table); + return true; +} + +static bool CloneCallSite( + JitFunctionContext* jitContext, int subQueryId, JitCallSite* source, JitCallSite* target, JitContextUsage usage) +{ + if (source->m_queryContext == nullptr) { + target->m_queryContext = nullptr; + target->m_queryString = DupString(source->m_queryString, usage); + if (target->m_queryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to clone call site query string: %s", + source->m_queryString); + return false; + } + MOT_LOG_TRACE("CloneCallSite(): Cloned string %p on %s scope into call site %p: %s", + target->m_queryString, + IsJitContextUsageGlobal(usage) ? "global" : "local", + target, + target->m_queryString); + } else { + target->m_queryContext = CloneJitContext(source->m_queryContext, usage); + if (target->m_queryContext == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone sub-query context for function JIT context"); + return false; + } + target->m_queryString = nullptr; + } + target->m_queryCmdType = source->m_queryCmdType; + + target->m_callParamCount = source->m_callParamCount; + if (target->m_callParamCount > 0) { + size_t allocSize = sizeof(JitCallParamInfo) * target->m_callParamCount; + target->m_callParamInfo = (JitCallParamInfo*)JitMemAlloc(allocSize, usage); + if (target->m_callParamInfo == nullptr) { + MOT_LOG_ERROR("Failed to allocate %u bytes for %d call parameters", allocSize, target->m_callParamCount); + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone sub-query parameters for function JIT context"); + return false; + } + errno_t erc = memcpy_s(target->m_callParamInfo, allocSize, source->m_callParamInfo, allocSize); + securec_check(erc, "\0", "\0"); + } else { + target->m_callParamInfo = nullptr; + } + + if (!CloneTupleDesc(source->m_tupDesc, &target->m_tupDesc, usage)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone sub-query tuple descriptor"); + return false; + } + target->m_exprIndex = source->m_exprIndex; + target->m_isUnjittableInvoke = source->m_isUnjittableInvoke; + target->m_isModStmt = source->m_isModStmt; + target->m_isInto = source->m_isInto; + return true; +} + +static bool CloneTupleDesc(TupleDesc source, TupleDesc* target, JitContextUsage usage) +{ + if (source != nullptr) { + MemoryContext oldCtx = CurrentMemoryContext; + if (IsJitContextUsageGlobal(usage)) { + CurrentMemoryContext = INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); + } else { + CurrentMemoryContext = SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); + } + *target = CreateTupleDescCopy(source); + CurrentMemoryContext = oldCtx; + if (*target == nullptr) { + return false; + } + } else { + *target = nullptr; + } + return true; +} + +static bool CloneResultDescriptors(JitFunctionContext* source, JitFunctionContext* target) +{ + JitContextUsage usage = target->m_usage; + + target->m_compositeResult = source->m_compositeResult; + + if (!CloneTupleDesc(source->m_resultTupDesc, &target->m_resultTupDesc, usage)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone result tuple descriptor for function JIT context"); + return false; + } + + if (!CloneTupleDesc(source->m_rowTupDesc, &target->m_rowTupDesc, usage)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone row tuple descriptor for function JIT context"); + return false; + } + + return true; +} + +static bool CloneJitFunctionContext(JitFunctionContext* source, JitFunctionContext* target, JitContextUsage usage) +{ + target->m_functionOid = source->m_functionOid; + target->m_functionTxnId = source->m_functionTxnId; + target->m_SPArgCount = source->m_SPArgCount; + target->m_SPSubQueryCount = source->m_SPSubQueryCount; + if (target->m_SPSubQueryCount == 0) { + target->m_SPSubQueryList = nullptr; + return true; + } + + // in any case of failure caller will call destroy function for safe cleanup + size_t allocSize = sizeof(JitCallSite) * target->m_SPSubQueryCount; + target->m_SPSubQueryList = (JitCallSite*)JitMemAlloc(allocSize, usage); + if (target->m_SPSubQueryList == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u sub-query data array in Function JIT context object", + allocSize, + (unsigned)target->m_SPSubQueryCount); + return false; + } + errno_t erc = memset_s(target->m_SPSubQueryList, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + for (uint32_t i = 0; i < target->m_SPSubQueryCount; ++i) { + JitCallSite* sourceCallSite = &source->m_SPSubQueryList[i]; + JitCallSite* targetCallSite = &target->m_SPSubQueryList[i]; + if (!CloneCallSite(target, i, sourceCallSite, targetCallSite, usage)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone call-site %d for function JIT context", i); + return false; + } + if (targetCallSite->m_queryContext != nullptr) { + targetCallSite->m_queryContext->m_parentContext = target; + } + } + target->m_paramCount = source->m_paramCount; + if (target->m_paramCount == 0) { + target->m_paramTypes = nullptr; + } else { + allocSize = sizeof(Oid) * target->m_paramCount; + target->m_paramTypes = (Oid*)JitMemAlloc(allocSize, usage); + } + if (target->m_paramTypes == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u sub-query parameter array in Function JIT context object", + allocSize, + target->m_paramCount); + return false; + } + erc = memcpy_s(target->m_paramTypes, allocSize, source->m_paramTypes, allocSize); + securec_check(erc, "\0", "\0"); + + if (!CloneResultDescriptors(source, target)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone result descriptors for function JIT context"); + return false; + } + + return true; } static inline List* GetSubExprTargetList(Query* query, Expr* expr, int subQueryIndex, int* subQueryCount); @@ -301,47 +789,140 @@ static List* GetSubQueryTargetList(const char* queryString, int subQueryIndex) return targetList; } -extern bool ReFetchIndices(JitContext* jitContext) +static bool RefetchJitFunctionContext(JitFunctionContext* functionContext) { - if (jitContext->m_commandType == JIT_COMMAND_INSERT) { + MOT_LOG_TRACE("Re-fetching tables and indices for function context %p with query: %s", + functionContext, + functionContext->m_queryString); + for (uint32_t i = 0; i < functionContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &functionContext->m_SPSubQueryList[i]; + if (callSite->m_queryContext != nullptr) { + MOT_LOG_TRACE("Re-fetching tables and indices for sub-query %u in function context %p with query: %s", + i, + functionContext, + callSite->m_queryContext->m_queryString); + if (!RefetchTablesAndIndices(callSite->m_queryContext)) { + return false; + } + } + } + return true; +} + +extern bool RefetchTablesAndIndices(MotJitContext* jitContext) +{ + MOT_LOG_TRACE( + "Re-fetching tables and indices for context %p with query: %s", jitContext, jitContext->m_queryString); + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + // handle function context + return RefetchJitFunctionContext((JitFunctionContext*)jitContext); + } + + // handle invoke context + JitQueryContext* queryContext = (JitQueryContext*)jitContext; + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + MOT_LOG_TRACE("Re-fetching tables and indices for INVOKE context %p with query: %s", + jitContext, + jitContext->m_queryString); + if (queryContext->m_invokeContext != nullptr) { + return RefetchTablesAndIndices(queryContext->m_invokeContext); + } return true; } - // re-fetch main index - if (jitContext->m_index == nullptr) { - jitContext->m_index = jitContext->m_table->GetIndexByExtId(jitContext->m_indexId); - if (jitContext->m_index == nullptr) { - MOT_LOG_TRACE("Failed to fetch index by extern id %" PRIu64 " for query: %s", - jitContext->m_indexId, - jitContext->m_queryString); + // re-fetch main table + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); + if (queryContext->m_table == nullptr) { + MOT_LOG_TRACE( + "Re-fetching main table in query context %p with query: %s", jitContext, jitContext->m_queryString); + queryContext->m_table = currTxn->GetTableByExternalId(queryContext->m_tableId); + if (queryContext->m_table == nullptr) { + MOT_LOG_TRACE("Failed to fetch table by extern id %" PRIu64 " for query: %s", + queryContext->m_tableId, + queryContext->m_queryString); return false; } - MOT_LOG_TRACE("Fetched index %s by extern id %" PRIu64 " for query: %s", - jitContext->m_index->GetName().c_str(), - jitContext->m_indexId, - jitContext->m_queryString); + MOT_LOG_TRACE("Fetched table %s (%p) by extern id %" PRIu64 " for query: %s", + queryContext->m_table->GetTableName().c_str(), + queryContext->m_table, + queryContext->m_tableId, + queryContext->m_queryString); } - // re-fetch inner index (JOIN commands only) - if (IsJoinCommand(jitContext->m_commandType)) { - if (jitContext->m_innerIndex == nullptr) { - jitContext->m_innerIndex = jitContext->m_innerTable->GetIndexByExtId(jitContext->m_innerIndexId); - if (jitContext->m_innerIndex == nullptr) { - MOT_LOG_TRACE("Failed to fetch inner index by extern id %" PRIu64, jitContext->m_innerIndexId); + // re-fetch main index + if (queryContext->m_index == nullptr) { + MOT_LOG_TRACE("Re-fetching indices for main table in query context %p with query: %s", + jitContext, + jitContext->m_queryString); + queryContext->m_index = queryContext->m_table->GetIndexByExtId(queryContext->m_indexId); + if (queryContext->m_index == nullptr) { + MOT_LOG_TRACE("Failed to fetch index by extern id %" PRIu64 " for query: %s", + queryContext->m_indexId, + queryContext->m_queryString); + return false; + } + MOT_LOG_TRACE("Fetched index %s (%p) by extern id %" PRIu64 " for query: %s", + queryContext->m_index->GetName().c_str(), + queryContext->m_index, + queryContext->m_indexId, + queryContext->m_queryString); + } + + // re-fetch inner table and index (JOIN commands only) + if (IsJoinCommand(queryContext->m_commandType)) { + if (queryContext->m_innerTable == nullptr) { + MOT_LOG_TRACE("Re-fetching inner table in JOIN query context %p with query: %s", + jitContext, + jitContext->m_queryString); + queryContext->m_innerTable = currTxn->GetTableByExternalId(queryContext->m_innerTableId); + if (queryContext->m_innerTable == nullptr) { + MOT_LOG_TRACE("Failed to fetch inner table by extern id %" PRIu64, queryContext->m_innerTableId); + return false; + } + } + + if (queryContext->m_innerIndex == nullptr) { + MOT_LOG_TRACE("Re-fetching indices for inner table in JOIN query context %p with query: %s", + jitContext, + jitContext->m_queryString); + queryContext->m_innerIndex = queryContext->m_innerTable->GetIndexByExtId(queryContext->m_innerIndexId); + if (queryContext->m_innerIndex == nullptr) { + MOT_LOG_TRACE("Failed to fetch inner index by extern id %" PRIu64, queryContext->m_innerIndexId); return false; } } } - // re-fetch sub-query indices (COMPOUND commands only) - if (jitContext->m_commandType == JIT_COMMAND_COMPOUND_SELECT) { - for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { - JitContext::SubQueryData* subQueryData = &jitContext->m_subQueryData[i]; - if (subQueryData->m_index == nullptr) { - subQueryData->m_index = subQueryData->m_table->GetIndexByExtId(subQueryData->m_indexId); - if (subQueryData->m_index == nullptr) { + // re-fetch sub-query tables and indices (COMPOUND commands only) + if (queryContext->m_commandType == JIT_COMMAND_COMPOUND_SELECT) { + MOT_LOG_TRACE("Re-fetching tables and indices for compound query context %p with query: %s", + jitContext, + jitContext->m_queryString); + for (uint32_t i = 0; i < queryContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &queryContext->m_subQueryContext[i]; + if (subQueryContext->m_table == nullptr) { + MOT_LOG_TRACE("Re-fetching table for compound sub-query %u in context %p with query: %s", + i, + jitContext, + jitContext->m_queryString); + subQueryContext->m_table = currTxn->GetTableByExternalId(subQueryContext->m_tableId); + if (subQueryContext->m_table == nullptr) { MOT_LOG_TRACE( - "Failed to fetch sub-query %u index by extern id %" PRIu64, i, subQueryData->m_indexId); + "Failed to fetch sub-query %u table by extern id %" PRIu64, i, subQueryContext->m_tableId); + return false; + } + } + + if (subQueryContext->m_index == nullptr) { + MOT_LOG_TRACE("Re-fetching indices for compound sub-query %u in context %p with query: %s", + i, + jitContext, + jitContext->m_queryString); + subQueryContext->m_index = subQueryContext->m_table->GetIndexByExtId(subQueryContext->m_indexId); + if (subQueryContext->m_index == nullptr) { + MOT_LOG_TRACE( + "Failed to fetch sub-query %u index by extern id %" PRIu64, i, subQueryContext->m_indexId); return false; } } @@ -351,205 +932,848 @@ extern bool ReFetchIndices(JitContext* jitContext) return true; } -static bool PrepareJitContextJoinData(JitContext* jitContext) +extern bool PrepareJitContext(MotJitContext* jitContext) { - // allocate inner loop search key for JOIN commands - if ((jitContext->m_innerSearchKey == nullptr) && IsJoinCommand(jitContext->m_commandType)) { - MOT_LOG_TRACE( - "Preparing inner search key for JOIN command from index %s", jitContext->m_innerIndex->GetName().c_str()); - jitContext->m_innerSearchKey = PrepareJitSearchKey(jitContext, jitContext->m_innerIndex); - if (jitContext->m_innerSearchKey == nullptr) { - MOT_LOG_TRACE( - "Failed to allocate reusable inner search key for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy + bool result = false; + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = PrepareJitQueryContext((JitQueryContext*)jitContext); + } else { + result = PrepareJitFunctionContext((JitFunctionContext*)jitContext); + } + // reset purged flag on success, otherwise make sure it is still raised, so that next execution round will attempt + // again to prepare the context before execution + if (jitContext->m_execState != nullptr) { + if (result) { + MOT_ATOMIC_STORE(jitContext->m_execState->m_purged, false); + } else { + MOT_ATOMIC_STORE(jitContext->m_execState->m_purged, true); + } + } + return result; +} + +static bool AllocJitQueryExecState(JitQueryContext* jitContext) +{ + size_t allocSize = sizeof(JitQueryExecState); + JitQueryExecState* execState = (JitQueryExecState*)MOT::MemSessionAlloc(allocSize); + if (execState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for query execution state", + (unsigned)allocSize); + return false; + } + + errno_t erc = memset_s(execState, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // allocate aggregate array + if (jitContext->m_aggCount > 0) { + allocSize = sizeof(JitAggExecState) * jitContext->m_aggCount; + execState->m_aggExecState = (JitAggExecState*)MOT::MemSessionAlloc(allocSize); + if (execState->m_aggExecState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for query aggregate execution state", + (unsigned)allocSize); + MOT::MemSessionFree(execState); + return false; } - MOT_LOG_TRACE("Prepared inner search key %p (%u bytes) for JOIN command from index %s", - jitContext->m_innerSearchKey, - jitContext->m_innerSearchKey->GetKeyLength(), - jitContext->m_innerIndex->GetName().c_str()); + errno_t erc = memset_s(execState->m_aggExecState, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } + + // allocate sub-query data array for compound commands + if (jitContext->m_commandType == JIT_COMMAND_COMPOUND_SELECT) { + execState->m_subQueryCount = jitContext->m_subQueryCount; + allocSize = sizeof(JitSubQueryExecState) * execState->m_subQueryCount; + execState->m_subQueryExecState = (JitSubQueryExecState*)MOT::MemSessionAlloc(allocSize); + if (execState->m_subQueryExecState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for sub-query data in query execution state", + (unsigned)allocSize); + MOT::MemSessionFree(execState->m_aggExecState); + execState->m_aggExecState = nullptr; + MOT::MemSessionFree(execState); + execState = nullptr; + return false; + } + + errno_t erc = memset_s(execState->m_subQueryExecState, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } + + if (jitContext->m_nonNativeSortParams) { + MOT_ASSERT(jitContext->m_commandType == JIT_COMMAND_RANGE_SELECT); + allocSize = sizeof(JitNonNativeSortExecState); + execState->m_nonNativeSortExecState = (JitNonNativeSortExecState*)MOT::MemSessionAlloc(allocSize); + + if (execState->m_nonNativeSortExecState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for JitNonNativeSortExecState in query execution state", + (unsigned)allocSize); + + if (execState->m_subQueryExecState) { + MOT::MemSessionFree(execState->m_subQueryExecState); + execState->m_subQueryExecState = nullptr; + } + + if (execState->m_aggExecState) { + MOT::MemSessionFree(execState->m_aggExecState); + execState->m_aggExecState = nullptr; + } + MOT::MemSessionFree(execState); + execState = nullptr; + + return false; + } + + execState->m_nonNativeSortExecState->m_tupleSort = nullptr; + } + + jitContext->m_execState = (JitExecState*)execState; + return true; +} + +static bool PrepareMainSearchKey(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + MOT_LOG_TRACE("Preparing search key from index %s", jitContext->m_index->GetName().c_str()); + execState->m_searchKey = PrepareJitSearchKey(jitContext, jitContext->m_index); + if (execState->m_searchKey == nullptr) { + MOT_LOG_TRACE("Failed to allocate reusable search key for JIT context, aborting jitted code execution"); + return false; + } + + MOT_LOG_TRACE("Prepared search key %p (%u bytes) from index %s", + execState->m_searchKey, + execState->m_searchKey->GetKeyLength(), + jitContext->m_index->GetName().c_str()); + return true; +} + +static bool PrepareUpdateBitmap(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + int fieldCount = (int)jitContext->m_table->GetFieldCount(); + MOT_LOG_TRACE("Initializing reusable bitmap set according to %d fields (including null-bits column 0) in table %s", + fieldCount, + jitContext->m_table->GetLongTableName().c_str()); + void* buf = MOT::MemSessionAlloc(sizeof(MOT::BitmapSet)); + if (buf == nullptr) { + MOT_LOG_TRACE("Failed to allocate reusable bitmap set for JIT context, aborting jitted code execution"); + return false; // safe cleanup during destroy + } + + uint8_t* bitmapData = (uint8_t*)MOT::MemSessionAlloc(MOT::BitmapSet::GetLength(fieldCount)); + if (bitmapData == nullptr) { + MOT_LOG_TRACE("Failed to allocate reusable bitmap set for JIT context, aborting jitted code execution"); + MOT::MemSessionFree(buf); + return false; // safe cleanup during destroy + } + execState->m_bitmapSet = new (buf) MOT::BitmapSet(bitmapData, fieldCount); + return true; +} + +static bool PrepareEndIteratorKey(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + MOT_LOG_TRACE("Preparing end iterator key for range update/select command from index %s", + jitContext->m_index->GetName().c_str()); + execState->m_endIteratorKey = PrepareJitSearchKey(jitContext, jitContext->m_index); + if (execState->m_endIteratorKey == nullptr) { + MOT_LOG_TRACE("Failed to allocate reusable end iterator key for JIT context, aborting jitted code execution"); + return false; // safe cleanup during destroy + } + + MOT_LOG_TRACE("Prepared end iterator key %p (%u bytes) for range update/select command from index %s", + execState->m_endIteratorKey, + execState->m_endIteratorKey->GetKeyLength(), + jitContext->m_index->GetName().c_str()); + return true; +} + +static bool PrepareInnerSrearchKey(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + MOT_LOG_TRACE( + "Preparing inner search key for JOIN command from index %s", jitContext->m_innerIndex->GetName().c_str()); + execState->m_innerSearchKey = PrepareJitSearchKey(jitContext, jitContext->m_innerIndex); + if (execState->m_innerSearchKey == nullptr) { + MOT_LOG_TRACE("Failed to allocate reusable inner search key for JIT context, aborting jitted code execution"); + return false; // safe cleanup during destroy + } + + MOT_LOG_TRACE("Prepared inner search key %p (%u bytes) for JOIN command from index %s", + execState->m_innerSearchKey, + execState->m_innerSearchKey->GetKeyLength(), + jitContext->m_innerIndex->GetName().c_str()); + return true; +} + +static bool PrepareInnerEndIteratorKey(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + MOT_LOG_TRACE( + "Preparing inner end iterator key for JOIN command from index %s", jitContext->m_innerIndex->GetName().c_str()); + execState->m_innerEndIteratorKey = PrepareJitSearchKey(jitContext, jitContext->m_innerIndex); + if (execState->m_innerEndIteratorKey == nullptr) { + MOT_LOG_TRACE( + "Failed to allocate reusable inner end iterator key for JIT context, aborting jitted code execution"); + return false; // safe cleanup during destroy + } + + MOT_LOG_TRACE("Prepared inner end iterator key %p (%u bytes) for JOIN command from index %s", + execState->m_innerEndIteratorKey, + execState->m_innerEndIteratorKey->GetKeyLength(), + jitContext->m_innerIndex->GetName().c_str()); + return true; +} + +static bool PrepareCompoundSubQuery(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + // allocate sub-query search keys and generate tuple table slot array using session top memory context + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + JitSubQueryExecState* subQueryExecState = &execState->m_subQueryExecState[i]; + if (subQueryExecState->m_tupleDesc == nullptr) { + MOT_LOG_TRACE("Preparing sub-query %u tuple descriptor", i); + List* targetList = GetSubQueryTargetList(jitContext->m_queryString, i); + if (targetList == nullptr) { + MOT_LOG_TRACE("Failed to locate sub-query %u target list", i); + return false; // safe cleanup during destroy + } else { + subQueryExecState->m_tupleDesc = ExecCleanTypeFromTL(targetList, false); + if (subQueryExecState->m_tupleDesc == nullptr) { + MOT_LOG_TRACE("Failed to create sub-query %u tuple descriptor from target list", i); + return false; // safe cleanup during destroy + } + Assert(subQueryExecState->m_tupleDesc->tdrefcount == -1); + } + } + if (subQueryExecState->m_slot == nullptr) { + MOT_ASSERT(subQueryExecState->m_tupleDesc != nullptr); + MOT_LOG_TRACE("Preparing sub-query %u result slot", i); + subQueryExecState->m_slot = MakeSingleTupleTableSlot(subQueryExecState->m_tupleDesc); + if (subQueryExecState->m_slot == nullptr) { + MOT_LOG_TRACE("Failed to generate sub-query %u tuple table slot", i); + return false; // safe cleanup during destroy + } + Assert(subQueryExecState->m_tupleDesc->tdrefcount == -1); + } + if (subQueryExecState->m_searchKey == nullptr) { + MOT_LOG_TRACE( + "Preparing sub-query %u search key from index %s", i, subQueryContext->m_index->GetName().c_str()); + subQueryExecState->m_searchKey = PrepareJitSearchKey(jitContext, subQueryContext->m_index); + if (subQueryExecState->m_searchKey == nullptr) { + MOT_LOG_TRACE("Failed to generate sub-query %u search key", i); + return false; // safe cleanup during destroy + } + } + if ((subQueryContext->m_commandType == JIT_COMMAND_AGGREGATE_RANGE_SELECT) && + (subQueryExecState->m_endIteratorKey == nullptr)) { + MOT_LOG_TRACE("Preparing sub-query %u end-iterator search key from index %s", + i, + subQueryContext->m_index->GetName().c_str()); + subQueryExecState->m_endIteratorKey = PrepareJitSearchKey(jitContext, subQueryContext->m_index); + if (subQueryExecState->m_endIteratorKey == nullptr) { + MOT_LOG_TRACE("Failed to generate sub-query %u end-iterator search key", i); + return false; // safe cleanup during destroy + } + } + } + return true; +} + +static bool PrepareInvokeContext(JitQueryContext* jitContext, JitQueryExecState* execState) +{ + const char* invokedQueryString = + jitContext->m_invokeContext ? jitContext->m_invokeContext->m_queryString : jitContext->m_invokedQueryString; + MOT_LOG_TRACE("Preparing INVOKE command parameter list for %u parameters into function: %s", + jitContext->m_invokeParamCount, + invokedQueryString); + + // it is possible after revalidation that number of parameter for INVOKE changes + if (jitContext->m_invokeParamCount == 0) { + if (execState->m_invokeParams != nullptr) { + MOT::MemSessionFree(execState->m_invokeParams); + execState->m_invokeParams = nullptr; + } + } else { + if (execState->m_invokeParams && + (execState->m_invokeParams->numParams != (int)jitContext->m_invokeParamCount)) { + MOT::MemSessionFree(execState->m_invokeParams); + execState->m_invokeParams = nullptr; + } + if (execState->m_invokeParams == nullptr) { + execState->m_invokeParams = CreateParamListInfo(jitContext->m_invokeParamCount, false); + if (execState->m_invokeParams == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT", + "Failed to prepare %u invoke parameter", + jitContext->m_invokeParamCount); + return false; // safe cleanup during destroy + } + } + } + MOT_LOG_TRACE("Prepared INVOKE command parameter list %p for %u parameters into function: %s", + execState->m_invokeParams, + jitContext->m_invokeParamCount, + invokedQueryString); + + if (jitContext->m_invokeContext != nullptr) { + if (!PrepareJitContext(jitContext->m_invokeContext)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Execute JIT", "Failed to prepare invoked function context: %s", invokedQueryString); + return false; // safe cleanup during destroy + } + } + return true; +} + +static bool PrepareJitQueryContext(JitQueryContext* jitContext) +{ + MOT_LOG_TRACE("Preparing context %p for query: %s", jitContext, jitContext->m_queryString); + // allocate execution state on-demand + if (jitContext->m_execState == nullptr) { + if (!AllocJitQueryExecState(jitContext)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Execute JIT Query", "Failed to allocate query execution state"); + return false; + } + } + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; + JitStatisticsProvider::GetInstance().AddSessionBytes((int64_t)sizeof(JitQueryExecState)); + + // re-fetch all table and index objects in case they were removed after TRUNCATE TABLE + if (!RefetchTablesAndIndices(jitContext)) { + MOT_LOG_TRACE("Failed to re-fetch table and index objects"); + return false; // safe cleanup during destroy + } + + jitContext->m_rc = MOT::RC_OK; + // allocate search key (except when executing INSERT or FULL-SCAN SELECT command) + bool allocSearchKey = ((execState->m_searchKey == nullptr) && IsCommandUsingIndex(jitContext->m_commandType) && + (jitContext->m_commandType != JIT_COMMAND_FULL_SELECT)); + if (allocSearchKey) { + if (!PrepareMainSearchKey(jitContext, execState)) { + return false; // safe cleanup during destroy + } + } + + // allocate bitmap-set object for incremental-redo when executing UPDATE command + bool allocBitmapSet = ((execState->m_bitmapSet == nullptr) && + ((jitContext->m_commandType == JIT_COMMAND_UPDATE) || + (jitContext->m_commandType == JIT_COMMAND_RANGE_UPDATE))); + if (allocBitmapSet) { + if (!PrepareUpdateBitmap(jitContext, execState)) { + return false; // safe cleanup during destroy + } + } + + // allocate end-iterator key object when executing range UPDATE command or special SELECT commands + bool allocEndItrKey = ((execState->m_endIteratorKey == nullptr) && IsRangeCommand(jitContext->m_commandType)); + if (allocEndItrKey) { + if (!PrepareEndIteratorKey(jitContext, execState)) { + return false; // safe cleanup during destroy + } + } + + // allocate inner loop search key for JOIN commands + bool allocInnerSearchKey = ((execState->m_innerSearchKey == nullptr) && IsJoinCommand(jitContext->m_commandType)); + if (allocInnerSearchKey) { + if (!PrepareInnerSrearchKey(jitContext, execState)) { + return false; // safe cleanup during destroy + } } // allocate inner loop end-iterator search key for JOIN commands - if ((jitContext->m_innerEndIteratorKey == nullptr) && IsJoinCommand(jitContext->m_commandType)) { - MOT_LOG_TRACE("Preparing inner end iterator key for JOIN command from index %s", - jitContext->m_innerIndex->GetName().c_str()); - jitContext->m_innerEndIteratorKey = PrepareJitSearchKey(jitContext, jitContext->m_innerIndex); - if (jitContext->m_innerEndIteratorKey == nullptr) { - MOT_LOG_TRACE( - "Failed to allocate reusable inner end iterator key for JIT context, aborting jitted code execution"); + bool allocInnerEndItrKey = + ((execState->m_innerEndIteratorKey == nullptr) && IsJoinCommand(jitContext->m_commandType)); + if (allocInnerEndItrKey) { + if (!PrepareInnerEndIteratorKey(jitContext, execState)) { return false; // safe cleanup during destroy } - - MOT_LOG_TRACE("Prepared inner end iterator key %p (%u bytes) for JOIN command from index %s", - jitContext->m_innerEndIteratorKey, - jitContext->m_innerEndIteratorKey->GetKeyLength(), - jitContext->m_innerIndex->GetName().c_str()); } // preparing outer row copy for JOIN commands - if ((jitContext->m_outerRowCopy == nullptr) && IsJoinCommand(jitContext->m_commandType)) { + bool allocOuterRowCopy = ((execState->m_outerRowCopy == nullptr) && IsJoinCommand(jitContext->m_commandType)); + if (allocOuterRowCopy) { MOT_LOG_TRACE("Preparing outer row copy for JOIN command"); - jitContext->m_outerRowCopy = jitContext->m_table->CreateNewRow(); - if (jitContext->m_outerRowCopy == nullptr) { + execState->m_outerRowCopy = jitContext->m_table->CreateNewRow(); + if (execState->m_outerRowCopy == nullptr) { MOT_LOG_TRACE("Failed to allocate reusable outer row copy for JIT context, aborting jitted code execution"); return false; // safe cleanup during destroy } } + // prepare sub-query data for COMPOUND commands + if (jitContext->m_commandType == JIT_COMMAND_COMPOUND_SELECT) { + MemoryContext oldCtx = CurrentMemoryContext; + CurrentMemoryContext = SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); + if (!PrepareCompoundSubQuery(jitContext, execState)) { + CurrentMemoryContext = oldCtx; + return false; // safe cleanup during destroy + } + CurrentMemoryContext = oldCtx; + } + + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + if (!PrepareInvokeContext(jitContext, execState)) { + return false; // safe cleanup during destroy + } + } + return true; } -static bool PrepareJitContextSubQueryData(JitContext* jitContext) +static bool AllocJitFunctionExecState(JitFunctionContext* jitContext) { - // allocate sub-query search keys and generate tuple table slot array using session top memory context - MemoryContext oldCtx = CurrentMemoryContext; - CurrentMemoryContext = SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); - for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { - JitContext::SubQueryData* subQueryData = &jitContext->m_subQueryData[i]; - if (subQueryData->m_tupleDesc == nullptr) { - MOT_LOG_TRACE("Preparing sub-query %u tuple descriptor", i); - List* targetList = GetSubQueryTargetList(jitContext->m_queryString, i); - if (targetList == nullptr) { - MOT_LOG_TRACE("Failed to locate sub-query %u target list", i); - CurrentMemoryContext = oldCtx; - return false; // safe cleanup during destroy - } + size_t allocSize = sizeof(JitFunctionExecState); + JitFunctionExecState* execState = (JitFunctionExecState*)MOT::MemSessionAlloc(allocSize); + if (execState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for function execution state", + (unsigned)allocSize); + return false; + } - subQueryData->m_tupleDesc = ExecCleanTypeFromTL(targetList, false); - if (subQueryData->m_tupleDesc == nullptr) { - MOT_LOG_TRACE("Failed to create sub-query %u tuple descriptor from target list", i); - CurrentMemoryContext = oldCtx; - return false; // safe cleanup during destroy - } + errno_t erc = memset_s(execState, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // we might have pure SPs (i.e. no queries into MOT tables), so check for count + if (jitContext->m_SPSubQueryCount > 0) { + allocSize = sizeof(JitInvokedQueryExecState) * jitContext->m_SPSubQueryCount; + execState->m_invokedQueryExecState = (JitInvokedQueryExecState*)MOT::MemSessionAlloc(allocSize); + if (execState->m_invokedQueryExecState == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT Query", + "Failed to allocate %u bytes for invoked-queries in function execution state", + (unsigned)allocSize); + MOT::MemSessionFree(execState); + return false; } - if (subQueryData->m_slot == nullptr) { - MOT_ASSERT(subQueryData->m_tupleDesc != nullptr); - MOT_LOG_TRACE("Preparing sub-query %u result slot", i); - subQueryData->m_slot = MakeSingleTupleTableSlot(subQueryData->m_tupleDesc); - if (subQueryData->m_slot == nullptr) { - MOT_LOG_TRACE("Failed to generate sub-query %u tuple table slot", i); - CurrentMemoryContext = oldCtx; - return false; // safe cleanup during destroy - } - } + erc = memset_s(execState->m_invokedQueryExecState, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } - if (subQueryData->m_searchKey == nullptr) { - MOT_LOG_TRACE( - "Preparing sub-query %u search key from index %s", i, subQueryData->m_index->GetName().c_str()); - subQueryData->m_searchKey = PrepareJitSearchKey(jitContext, subQueryData->m_index); - if (subQueryData->m_searchKey == nullptr) { - MOT_LOG_TRACE("Failed to generate sub-query %u search key", i); - CurrentMemoryContext = oldCtx; - return false; // safe cleanup during destroy - } - } + jitContext->m_execState = (JitExecState*)execState; - if ((subQueryData->m_commandType == JIT_COMMAND_AGGREGATE_RANGE_SELECT) && - (subQueryData->m_endIteratorKey == nullptr)) { - MOT_LOG_TRACE("Preparing sub-query %u end-iterator search key from index %s", - i, - subQueryData->m_index->GetName().c_str()); - subQueryData->m_endIteratorKey = PrepareJitSearchKey(jitContext, subQueryData->m_index); - if (subQueryData->m_endIteratorKey == nullptr) { - MOT_LOG_TRACE("Failed to generate sub-query %u end-iterator search key", i); - CurrentMemoryContext = oldCtx; - return false; // safe cleanup during destroy + return true; +} + +static void GetTypeDescriptor(PLpgSQL_type* typeDesc, Form_pg_attribute attr) +{ + typeDesc->dtype = PLPGSQL_DTYPE_VAR; + typeDesc->dno = 0; // not used, right? + typeDesc->ispkg = false; + typeDesc->ttype = PLPGSQL_TTYPE_SCALAR; + typeDesc->typoid = attr->atttypid; + typeDesc->atttypmod = attr->atttypmod; + typeDesc->collation = attr->attcollation; + typeDesc->typbyval = attr->attbyval; + typeDesc->typlen = attr->attlen; + typeDesc->typname = attr->attname.data; + typeDesc->typrelid = attr->attrelid; + + Oid typeInputOid = InvalidOid; + getTypeInputInfo(attr->atttypid, &typeInputOid, &typeDesc->typioparam); + fmgr_info(typeInputOid, &typeDesc->typinput); +} + +static bool PrepareCallSite( + JitFunctionContext* jitContext, JitInvokedQueryExecState* execState, JitCallSite* callSite, int subQueryId) +{ + // a sub-query result tuple might change, so we regenerate unconditionally work slot and result slot + const char* queryType = callSite->m_queryContext ? "jittable" : "non-jittable"; + const char* queryString = + callSite->m_queryContext ? callSite->m_queryContext->m_queryString : callSite->m_queryString; + if (queryString == nullptr) { + MOT_LOG_TRACE("Invalid call site: missing query string"); + return false; + } + MOT_LOG_TRACE( + "Preparing stored-procedure %s sub-query %u invoke exec state at: %p", queryType, subQueryId, execState); + MOT_LOG_TRACE("Preparing stored-procedure %s sub-query %u work slot: %s", queryType, subQueryId, queryString); + MOT_ASSERT(callSite->m_tupDesc != nullptr); + Assert(callSite->m_tupDesc->tdrefcount == -1); + if (execState->m_workSlot != nullptr) { + execState->m_workSlot->tts_tupleDescriptor = nullptr; + ExecDropSingleTupleTableSlot(execState->m_workSlot); + execState->m_workSlot = nullptr; + } + execState->m_workSlot = MakeSingleTupleTableSlot(callSite->m_tupDesc); + if (execState->m_workSlot == nullptr) { + MOT_LOG_TRACE("Failed to generate stored-procedure %s sub-query %u work tuple table slot: %s", + queryType, + subQueryId, + queryString); + return false; // safe cleanup during destroy + } + Assert(callSite->m_tupDesc->tdrefcount == -1); + + MOT_LOG_TRACE("Preparing stored-procedure %s sub-query %u result slot: %s", queryType, subQueryId, queryString); + if (execState->m_resultSlot != nullptr) { + execState->m_resultSlot->tts_tupleDescriptor = nullptr; + ExecDropSingleTupleTableSlot(execState->m_resultSlot); + execState->m_resultSlot = nullptr; + } + execState->m_resultSlot = MakeSingleTupleTableSlot(callSite->m_tupDesc); + if (execState->m_resultSlot == nullptr) { + MOT_LOG_TRACE("Failed to generate stored-procedure %s sub-query %u result tuple table slot: %s", + queryType, + subQueryId, + queryString); + return false; // safe cleanup during destroy + } + Assert(callSite->m_tupDesc->tdrefcount == -1); + + // a sub-query parameter list might change (in size and/or types), so we regenerate it unconditionally + MOT_LOG_TRACE("Preparing stored-procedure %s sub-query %u parameters list of size %d: %s", + queryType, + subQueryId, + jitContext->m_paramCount, + queryString); + if (execState->m_params != nullptr) { + MOT::MemSessionFree(execState->m_params); + execState->m_params = nullptr; + } + + MOT_LOG_TRACE("Preparing stored-procedure %s sub-query %u param list: %s", queryType, subQueryId, queryString); + execState->m_params = CreateParamListInfo(jitContext->m_paramCount, false); + if (execState->m_params == nullptr) { + MOT_LOG_TRACE("Failed to clone %s sub-query %u parameter list: %s", queryType, subQueryId, queryString); + return false; // safe cleanup during destroy + } + for (int i = 0; i < execState->m_params->numParams; ++i) { + execState->m_params->params[i].isnull = true; + execState->m_params->params[i].ptype = jitContext->m_paramTypes[i]; + execState->m_params->params[i].pflags = 0; + MOT_LOG_DEBUG("param-type: %u", execState->m_params->params[i].ptype); + } + + if (execState->m_resultTypes == nullptr) { + uint32_t attrCount = (uint32_t)execState->m_resultSlot->tts_tupleDescriptor->natts; + if (attrCount > 0) { + uint32_t allocSize = sizeof(PLpgSQL_type) * attrCount; + execState->m_resultTypes = (PLpgSQL_type*)MOT::MemSessionAlloc(allocSize); + if (execState->m_resultTypes == nullptr) { + MOT_LOG_TRACE("Failed to allocate %u bytes for %u result type descriptors", allocSize, attrCount); + return false; + } + for (uint32_t i = 0; i < attrCount; ++i) { + PLpgSQL_type* typeDesc = &execState->m_resultTypes[i]; + Form_pg_attribute attr = execState->m_resultSlot->tts_tupleDescriptor->attrs[i]; + GetTypeDescriptor(typeDesc, attr); } } } - CurrentMemoryContext = oldCtx; + + // NOTE: JIT context of each sub-query is prepared (in PrepareCallSitePlans) only after the call site plans + // are generated. This will ensure we have necessary locks for the sub-queries. + return true; } -extern bool PrepareJitContext(JitContext* jitContext) +class CallSitePlanGenerator : public JitFunctionQueryVisitor { +public: + explicit CallSitePlanGenerator(JitFunctionContext* jitContext) : m_jitContext(jitContext) + { + BuildExprQueryMap(); + m_queryPlans.resize(m_jitContext->m_SPSubQueryCount, false); + } + + ~CallSitePlanGenerator() final + {} + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int exprIndex, bool into) final + { + // find call site by expression index + int queryIndex = GetQueryIndex(exprIndex); + if (queryIndex == -1) { + MOT_LOG_DEBUG("Cannot find call site for query with expression index %d: %s", exprIndex, expr->query); + return JitVisitResult::JIT_VISIT_CONTINUE; + } + JitCallSite* callSite = &m_jitContext->m_SPSubQueryList[queryIndex]; + JitFunctionExecState* functionExecState = (JitFunctionExecState*)m_jitContext->m_execState; + JitInvokedQueryExecState* execState = &functionExecState->m_invokedQueryExecState[queryIndex]; + const char* queryType = callSite->m_queryContext ? "jittable" : "non-jittable"; + const char* queryString = expr->query; + + if (execState->m_plan != nullptr) { + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + // invoke call site does not need SPI plan + if ((callSite->m_queryContext != nullptr) && (callSite->m_queryContext->m_commandType == JIT_COMMAND_INVOKE)) { + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + MOT_LOG_TRACE("Preparing stored-procedure %s sub-query %u plan: %s", queryType, queryIndex, queryString); + execState->m_plan = GetSpiPlan(functionExecState->m_function, expr); + if (execState->m_plan == nullptr) { + MOT_LOG_TRACE("Failed to prepare SPI plan for %s sub-query %u: %s", queryType, queryIndex, queryString); + return JitVisitResult::JIT_VISIT_ERROR; // safe cleanup during destroy + } + + // setup parameter list + execState->m_expr = expr; + execState->m_params->paramFetch = plpgsql_param_fetch; + execState->m_params->paramFetchArg = &functionExecState->m_estate; + execState->m_params->parserSetup = (ParserSetupHook)plpgsql_parser_setup; + execState->m_params->parserSetupArg = (void*)execState->m_expr; + execState->m_params->params_need_process = false; + + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + void OnError(const char* stmtType, int lineNo) final + { + MOT_LOG_TRACE("Failed to generate call site plan while processing statement at line %d: %s", lineNo, stmtType); + } + + bool AllQueryPlansGenerated() + { + for (uint32_t i = 0; i < m_jitContext->m_SPSubQueryCount; ++i) { + if (!m_queryPlans[i]) { + MOT_LOG_TRACE("Query plan for call site %u not generated", i); + return false; + } + } + return true; + } + +private: + JitFunctionContext* m_jitContext; + using JitExprQueryMap = std::map; + JitExprQueryMap m_exprQueryMap; + std::vector m_queryPlans; + + void BuildExprQueryMap() + { + for (uint32_t i = 0; i < m_jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &m_jitContext->m_SPSubQueryList[i]; + m_exprQueryMap.insert(JitExprQueryMap::value_type(callSite->m_exprIndex, (int)i)); + } + } + + int GetQueryIndex(int exprIndex) + { + int queryIndex = -1; + JitExprQueryMap::iterator itr = m_exprQueryMap.find(exprIndex); + if (itr != m_exprQueryMap.end()) { + queryIndex = itr->second; + if (m_queryPlans[queryIndex] == true) { // query hit twice + MOT_LOG_TRACE("Query %d hit twice while generating call site plans", queryIndex); + queryIndex = -1; + } else { + m_queryPlans[queryIndex] = true; + } + } + return queryIndex; + } +}; + +static bool PrepareCallSitePlans(JitFunctionContext* jitContext) { - // allocate argument-is-null array - if (jitContext->m_argIsNull == nullptr) { - MOT_LOG_TRACE("Allocating null argument array with %u slots", (unsigned)jitContext->m_argCount); - jitContext->m_argIsNull = (int*)MOT::MemSessionAlloc(sizeof(int) * jitContext->m_argCount); - if (jitContext->m_argIsNull == nullptr) { - MOT_LOG_TRACE("Failed to allocate null argument array in size of %d slots", jitContext->m_argCount); + CallSitePlanGenerator planGenerator(jitContext); + JitFunctionExecState* functionExecState = (JitFunctionExecState*)jitContext->m_execState; + + SPIAutoConnect spiAutoConnect; + if (!spiAutoConnect.IsConnected()) { + int rc = spiAutoConnect.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while generating SPI plans for jitted function: %s", + jitContext->m_queryString, + SPI_result_code_string(rc), + rc); + return false; + } + + if (!VisitFunctionQueries(functionExecState->m_function, &planGenerator)) { + MOT_LOG_TRACE("Failed to generate call site plans: error encountered"); + return false; + } + + if (!planGenerator.AllQueryPlansGenerated()) { + MOT_LOG_TRACE("Failed to generate call site plans: not all plans generated"); + return false; + } + + volatile bool result = true; + volatile CachedPlan* cplan = nullptr; + volatile JitInvokedQueryExecState* invokeExecState = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + // Prepare JIT context of each sub-query only after the call site plans are generated. This will ensure we have + // necessary locks for the sub-queries. + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + // prepare JIT context state of each invoked query + if (callSite->m_queryContext != nullptr) { + MOT_LOG_TRACE("Preparing stored-procedure jittable sub-query %u context: %s", + i, + callSite->m_queryContext->m_queryString); + + // in case we hit parsing during revalidate, we must make sure we have up-to-date parameters + invokeExecState = &functionExecState->m_invokedQueryExecState[i]; + functionExecState->m_estate.cur_expr = invokeExecState->m_expr; // required by plpgsql_param_fetch + if (invokeExecState->m_expr != nullptr) { + invokeExecState->m_expr->func = functionExecState->m_function; + invokeExecState->m_expr->func->cur_estate = (PLpgSQL_execstate*)&functionExecState->m_estate; + } + + if (callSite->m_queryContext->m_commandType != JitExec::JIT_COMMAND_INVOKE) { + cplan = SPI_plan_get_cached_plan(invokeExecState->m_plan); + if (cplan == nullptr) { + MOT_LOG_ERROR("Failed to get cached plan"); + result = false; + break; + } + } + + result = PrepareJitContext(callSite->m_queryContext); + + if (cplan != nullptr) { + ReleaseCachedPlan((CachedPlan*)cplan, invokeExecState->m_plan->saved); + cplan = nullptr; + } + + if (!result) { + MOT_LOG_TRACE("Failed to prepare sub-query %u context", i); + break; // safe cleanup during destroy + } + } + } + + MOT_ASSERT(cplan == nullptr); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + + if (cplan != nullptr) { + ReleaseCachedPlan((CachedPlan*)cplan, invokeExecState->m_plan->saved); + cplan = nullptr; + } + + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while preparing sub-query contexts for function %s: %s", + jitContext->m_queryString, + edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while preparing sub-query contexts for function %s: %s", + jitContext->m_queryString, + edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + result = false; + } + PG_END_TRY(); + + return result; +} + +static bool PrepareJitFunctionContext(JitFunctionContext* jitContext) +{ + MOT_LOG_TRACE("Preparing context %p for function: %s", jitContext, jitContext->m_queryString); + // allocate execution state on-demand + if (jitContext->m_execState == nullptr) { + if (!AllocJitFunctionExecState(jitContext)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Execute JIT Query", "Failed to allocate function execution state"); + return false; + } + } + JitStatisticsProvider::GetInstance().AddSessionBytes((int64_t)sizeof(JitFunctionExecState)); + + // prepare compiled function + JitFunctionExecState* execState = (JitFunctionExecState*)jitContext->m_execState; + if (execState->m_function == nullptr) { + execState->m_function = GetPGCompiledFunction(jitContext->m_functionOid); + if (execState->m_function == nullptr || execState->m_function->fn_xmin != jitContext->m_functionTxnId) { + bool functionReplaced = false; + if (execState->m_function == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_CONCURRENT_MODIFICATION, "Execute JIT Query", "Failed to retrieve PG function"); + } else { + functionReplaced = true; + if (execState->m_function->fn_xmin > jitContext->m_functionTxnId) { + MOT_REPORT_ERROR(MOT_ERROR_CONCURRENT_MODIFICATION, + "Execute JIT Query", + "PG function definition changed from %" PRIu64 " to %" PRIu64 ", JIT context has older version", + jitContext->m_functionTxnId, + execState->m_function->fn_xmin); + } else { + MOT_REPORT_ERROR(MOT_ERROR_CONCURRENT_MODIFICATION, + "Execute JIT Query", + "PG function definition changed from %" PRIu64 " to %" PRIu64 ", JIT context has newer version", + execState->m_function->fn_xmin, + jitContext->m_functionTxnId); + } + } + + // Expire the JIT source only if the function was dropped or the JIT context/source is based on older + // definition of the function. + // It is possible that JIT context/source is already based on the newer definition of the function, but + // this session has not yet seen the newer definition. In this case, GetPGCompiledFunction will retrieve + // the older function, so we should not expire the JIT source. + if (execState->m_function == nullptr || execState->m_function->fn_xmin > jitContext->m_functionTxnId) { + bool expireInvokeQuery = false; + LockJitSource(jitContext->m_jitSource); + if (jitContext->m_jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + expireInvokeQuery = true; + // We are not expiring the JIT source in a transactional manner, so we cannot set the correct + // m_expireTxnId here. But this is fine, we can still avoid premature revalidation attempts using + // m_functionTxnId itself. + SetJitSourceExpired(jitContext->m_jitSource, 0, functionReplaced); + } + UnlockJitSource(jitContext->m_jitSource); + if (expireInvokeQuery) { + MotJitContext* invokeQueryContext = jitContext->m_parentContext; + LockJitSource(invokeQueryContext->m_jitSource); + // We are not expiring the JIT source in a transactional manner, so we cannot set the correct + // m_expireTxnId here. But this is fine, we can still avoid premature revalidation attempts using + // m_functionTxnId itself. + SetJitSourceExpired(invokeQueryContext->m_jitSource, 0, functionReplaced); + UnlockJitSource(invokeQueryContext->m_jitSource); + } + } + + // we can mark this context as invalid outside lock-scope even if there is a race with compilation done + // event, because both will lead to revalidation, so order of events is not important (see plancache.cpp) + InvalidateJitContext(jitContext, 0); + execState->m_function = nullptr; + return false; + } + ++execState->m_function->use_count; + MOT_LOG_TRACE("PrepareJitFunctionContext(): Increased use count of function %p to %lu: %s", + execState->m_function, + execState->m_function->use_count, + jitContext->m_queryString); + } + + // prepare minimal execution state if required + if (execState->m_function != execState->m_estate.func) { + PrepareExecState(&execState->m_estate, execState->m_function); + } + + // prepare call sites + MemoryContext oldCtx = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (!PrepareCallSite(jitContext, &execState->m_invokedQueryExecState[i], callSite, i)) { + (void)MemoryContextSwitchTo(oldCtx); return false; } } - // re-fetch all index objects in case they were removed after TRUNCATE TABLE - if (jitContext->m_commandType != JIT_COMMAND_INSERT) { - if (!ReFetchIndices(jitContext)) { - return false; // safe cleanup during destroy - } + if (!PrepareCallSitePlans(jitContext)) { + (void)MemoryContextSwitchTo(oldCtx); + return false; } - // allocate search key (except when executing INSERT or FULL-SCAN SELECT command) - if ((jitContext->m_searchKey == nullptr) && (jitContext->m_commandType != JIT_COMMAND_INSERT) && - (jitContext->m_commandType != JIT_COMMAND_FULL_SELECT)) { - MOT_LOG_TRACE("Preparing search key from index %s", jitContext->m_index->GetName().c_str()); - jitContext->m_searchKey = PrepareJitSearchKey(jitContext, jitContext->m_index); - if (jitContext->m_searchKey == nullptr) { - MOT_LOG_TRACE("Failed to allocate reusable search key for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy - } - - MOT_LOG_TRACE("Prepared search key %p (%u bytes) from index %s", - jitContext->m_searchKey, - jitContext->m_searchKey->GetKeyLength(), - jitContext->m_index->GetName().c_str()); - } - - // allocate bitmap-set object for incremental-redo when executing UPDATE command - if ((jitContext->m_bitmapSet == nullptr) && ((jitContext->m_commandType == JIT_COMMAND_UPDATE) || - (jitContext->m_commandType == JIT_COMMAND_RANGE_UPDATE))) { - int fieldCount = (int)jitContext->m_table->GetFieldCount(); - MOT_LOG_TRACE( - "Initializing reusable bitmap set according to %d fields (including null-bits column 0) in table %s", - fieldCount, - jitContext->m_table->GetLongTableName().c_str()); - void* buf = MOT::MemSessionAlloc(sizeof(MOT::BitmapSet)); - if (buf == nullptr) { - MOT_LOG_TRACE("Failed to allocate reusable bitmap set for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy - } - - uint8_t* bitmapData = (uint8_t*)MOT::MemSessionAlloc(MOT::BitmapSet::GetLength(fieldCount)); - if (bitmapData == nullptr) { - MOT_LOG_TRACE("Failed to allocate reusable bitmap set for JIT context, aborting jitted code execution"); - MOT::MemSessionFree(buf); - return false; // safe cleanup during destroy - } - jitContext->m_bitmapSet = new (buf) MOT::BitmapSet(bitmapData, fieldCount); - } - - // allocate end-iterator key object when executing range UPDATE command or special SELECT commands - if ((jitContext->m_endIteratorKey == nullptr) && IsRangeCommand(jitContext->m_commandType)) { - MOT_LOG_TRACE("Preparing end iterator key for range update/select command from index %s", - jitContext->m_index->GetName().c_str()); - jitContext->m_endIteratorKey = PrepareJitSearchKey(jitContext, jitContext->m_index); - if (jitContext->m_endIteratorKey == nullptr) { - MOT_LOG_TRACE( - "Failed to allocate reusable end iterator key for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy - } - - MOT_LOG_TRACE("Prepared end iterator key %p (%u bytes) for range update/select command from index %s", - jitContext->m_endIteratorKey, - jitContext->m_endIteratorKey->GetKeyLength(), - jitContext->m_index->GetName().c_str()); - } - - if (!PrepareJitContextJoinData(jitContext)) { - MOT_LOG_TRACE("Failed to allocate join related data for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy - } - - // prepare sub-query data for COMPOUND commands - if (jitContext->m_commandType == JIT_COMMAND_COMPOUND_SELECT) { - if (!PrepareJitContextSubQueryData(jitContext)) { - MOT_LOG_TRACE("Failed to sub-query data for JIT context, aborting jitted code execution"); - return false; // safe cleanup during destroy - } - } + (void)MemoryContextSwitchTo(oldCtx); return true; } @@ -560,235 +1784,1952 @@ static void DestroyDatumArray(JitDatumArray* datumArray, JitContextUsage usage) MOT_ASSERT(datumArray->m_datums != nullptr); for (uint32_t i = 0; i < datumArray->m_datumCount; ++i) { if (!datumArray->m_datums[i].m_isNull && !IsPrimitiveType(datumArray->m_datums[i].m_type)) { - if (usage == JIT_CONTEXT_GLOBAL) { - MOT::MemGlobalFree(DatumGetPointer(datumArray->m_datums[i].m_datum)); - } else { - MOT::MemSessionFree(DatumGetPointer(datumArray->m_datums[i].m_datum)); - } + JitMemFree(DatumGetPointer(datumArray->m_datums[i].m_datum), usage); } } - if (usage == JIT_CONTEXT_GLOBAL) { - MOT::MemGlobalFree(datumArray->m_datums); - } else { - MOT::MemSessionFree(datumArray->m_datums); - } + JitMemFree(datumArray->m_datums, usage); datumArray->m_datums = nullptr; datumArray->m_datumCount = 0; } + + MOT_ASSERT(datumArray->m_datums == nullptr); } -extern void DestroyJitContext(JitContext* jitContext) +extern void DestroyJitContext(MotJitContext* jitContext, bool isDropCachedPlan /* = false */) +{ + if (jitContext == nullptr) { + return; + } + + if (isDropCachedPlan) { + (void)EnsureSafeThreadAccess(); + } + +#ifdef MOT_JIT_DEBUG + MOT_LOG_TRACE("Destroying JIT context %p with %" PRIu64 " executions of query: %s", + jitContext, + jitContext->m_execState ? jitContext->m_execState->m_execCount : 0, + jitContext->m_queryString); +#else + MOT_LOG_TRACE("Destroying %s JIT %s context %p of query: %s", + JitContextUsageToString(jitContext->m_usage), + JitContextTypeToString(jitContext->m_contextType), + jitContext, + jitContext->m_queryString); +#endif + + // remove from JIT source + if (jitContext->m_jitSource != nullptr) { + RemoveJitSourceContext(jitContext->m_jitSource, jitContext); + jitContext->m_jitSource = nullptr; + } + + // cleanup constant datum array + DestroyDatumArray(&jitContext->m_constDatums, jitContext->m_usage); + + // cleanup + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + DestroyJitQueryContext((JitQueryContext*)jitContext, isDropCachedPlan); + } else { + DestroyJitFunctionContext((JitFunctionContext*)jitContext, isDropCachedPlan); + } + + // cleanup code generator (only in global-usage, not even global-secondary usage) + if ((jitContext->m_codeGen != nullptr) && (jitContext->m_usage == JIT_CONTEXT_GLOBAL)) { + FreeGsCodeGen(jitContext->m_codeGen); + jitContext->m_codeGen = nullptr; + } + + // return context to pool + FreeJitContext(jitContext); +} + +static void DestroyJitNonNativeSortExecState( + JitNonNativeSortExecState* jitNonNativeSortExecState, JitContextUsage usage) +{ + MOT_ASSERT(jitNonNativeSortExecState); + + if (jitNonNativeSortExecState->m_tupleSort) { + tuplesort_end((Tuplesortstate*)jitNonNativeSortExecState->m_tupleSort); + jitNonNativeSortExecState->m_tupleSort = nullptr; + } + + JitMemFree(jitNonNativeSortExecState, usage); +} + +static void DestroyJitExecState(JitExecState* execState) +{ + if (execState != nullptr) { + MOT::MemSessionFree(execState); + } +} + +static void CleanupExecStatePrimary(JitQueryContext* jitContext, JitQueryExecState* execState, bool isDropCachedPlan) +{ + if (execState == nullptr) { + return; + } + + if (jitContext->m_table == nullptr) { + return; + } + + MOT::Table* table = nullptr; + if (isDropCachedPlan) { + // If this is called from DropCachedPlan, lock the table. Because for deallocate and deallocate all, there is + // no table locks taken in the envelope, so we must lock the MOT::Table before destroying keys and rows. + if (u_sess->mot_cxt.txn_manager != nullptr) { + table = u_sess->mot_cxt.txn_manager->GetTxnTable(jitContext->m_tableId); + if (table != nullptr) { + table->RdLock(); + } + } else { + table = MOT::GetTableManager()->GetTableSafeByExId(jitContext->m_tableId); + } + if (table == nullptr) { + // Table is dropped concurrently, just set everything to nullptr and return. + execState->m_searchKey = nullptr; + execState->m_endIteratorKey = nullptr; + execState->m_beginIterator = nullptr; + execState->m_endIterator = nullptr; + execState->m_outerRowCopy = nullptr; + return; + } + MOT_ASSERT(table == jitContext->m_table); + } + + if (jitContext->m_index != nullptr) { + MOT::Index* index = jitContext->m_index; + if (table != nullptr) { + index = table->GetIndexByExtId(jitContext->m_indexId); + } else { + index = jitContext->m_table->GetIndexByExtId(jitContext->m_indexId); + } + + if (jitContext->m_index == index) { + // Destroy the keys and iterators only if the index pointer has not changed. + if (execState->m_searchKey != nullptr) { + index->DestroyKey(execState->m_searchKey); + } + + if (execState->m_endIteratorKey != nullptr) { + index->DestroyKey(execState->m_endIteratorKey); + } + + if (execState->m_beginIterator != nullptr) { + destroyIterator(execState->m_beginIterator); + } + + if (execState->m_endIterator != nullptr) { + destroyIterator(execState->m_endIterator); + } + } + + execState->m_searchKey = nullptr; + execState->m_endIteratorKey = nullptr; + execState->m_beginIterator = nullptr; + execState->m_endIterator = nullptr; + } + + // cleanup JOIN outer row copy + if (execState->m_outerRowCopy != nullptr) { + jitContext->m_table->DestroyRow(execState->m_outerRowCopy); + execState->m_outerRowCopy = nullptr; + } + + if (table != nullptr) { + table->Unlock(); + } +} + +static void CleanupExecStateInner(JitQueryContext* jitContext, JitQueryExecState* execState, bool isDropCachedPlan) +{ + if (execState == nullptr) { + return; + } + + if (jitContext->m_innerTable == nullptr) { + return; + } + + MOT::Table* table = nullptr; + if (isDropCachedPlan) { + // If this is called from DropCachedPlan, lock the table. Because for deallocate and deallocate all, there is + // no table locks taken in the envelope, so we must lock the MOT::Table before destroying keys and rows. + if (u_sess->mot_cxt.txn_manager != nullptr) { + table = u_sess->mot_cxt.txn_manager->GetTxnTable(jitContext->m_innerTableId); + if (table != nullptr) { + table->RdLock(); + } + } else { + table = MOT::GetTableManager()->GetTableSafeByExId(jitContext->m_innerTableId); + } + if (table == nullptr) { + // Table is dropped concurrently, just set everything to nullptr and return. + execState->m_innerSearchKey = nullptr; + execState->m_innerEndIteratorKey = nullptr; + execState->m_innerBeginIterator = nullptr; + execState->m_innerEndIterator = nullptr; + execState->m_innerRow = nullptr; + return; + } + MOT_ASSERT(table == jitContext->m_innerTable); + } + + if (jitContext->m_innerIndex != nullptr) { + MOT::Index* index = jitContext->m_innerIndex; + if (table != nullptr) { + index = table->GetIndexByExtId(jitContext->m_innerIndexId); + } else { + index = jitContext->m_innerTable->GetIndexByExtId(jitContext->m_innerIndexId); + } + + if (jitContext->m_innerIndex == index) { + // Destroy the keys and iterators only if the index pointer has not changed. + if (execState->m_innerSearchKey != nullptr) { + index->DestroyKey(execState->m_innerSearchKey); + } + + if (execState->m_innerEndIteratorKey != nullptr) { + index->DestroyKey(execState->m_innerEndIteratorKey); + } + + if (execState->m_innerBeginIterator != nullptr) { + destroyIterator(execState->m_innerBeginIterator); + } + + if (execState->m_innerEndIterator != nullptr) { + destroyIterator(execState->m_innerEndIterator); + } + } + + execState->m_innerSearchKey = nullptr; + execState->m_innerEndIteratorKey = nullptr; + execState->m_innerBeginIterator = nullptr; + execState->m_innerEndIterator = nullptr; + } + + // cleanup JOIN inner row + if (execState->m_innerRow != nullptr) { + jitContext->m_innerTable->DestroyRow(execState->m_innerRow); + execState->m_innerRow = nullptr; + } + + if (table != nullptr) { + table->Unlock(); + } +} + +static void CleanupSubQueryExecState( + JitSubQueryContext* subQueryContext, JitSubQueryExecState* subQueryExecState, bool isDropCachedPlan) +{ + if (subQueryExecState->m_slot != nullptr) { + ExecDropSingleTupleTableSlot(subQueryExecState->m_slot); + subQueryExecState->m_slot = nullptr; + } + + if (subQueryExecState->m_tupleDesc != nullptr) { + FreeTupleDesc(subQueryExecState->m_tupleDesc); + subQueryExecState->m_tupleDesc = nullptr; + } + + if (subQueryContext->m_table == nullptr) { + return; + } + + if (subQueryContext->m_index == nullptr) { + return; + } + + MOT::Table* table = nullptr; + if (isDropCachedPlan) { + // If this is called from DropCachedPlan, lock the table. Because for deallocate and deallocate all, there is + // no table locks taken in the envelope, so we must lock the MOT::Table before destroying keys. + if (u_sess->mot_cxt.txn_manager != nullptr) { + table = u_sess->mot_cxt.txn_manager->GetTxnTable(subQueryContext->m_tableId); + if (table != nullptr) { + table->RdLock(); + } + } else { + table = MOT::GetTableManager()->GetTableSafeByExId(subQueryContext->m_tableId); + } + if (table == nullptr) { + // Table is dropped concurrently, just set everything to nullptr and return. + subQueryExecState->m_searchKey = nullptr; + subQueryExecState->m_endIteratorKey = nullptr; + return; + } + MOT_ASSERT(table == subQueryContext->m_table); + } + + MOT::Index* index = subQueryContext->m_index; + if (table != nullptr) { + index = table->GetIndexByExtId(subQueryContext->m_indexId); + } else { + index = subQueryContext->m_table->GetIndexByExtId(subQueryContext->m_indexId); + } + + if (subQueryContext->m_index == index) { + // Destroy the keys only if the index pointer has not changed. + if (subQueryExecState->m_searchKey != nullptr) { + index->DestroyKey(subQueryExecState->m_searchKey); + } + + if (subQueryExecState->m_endIteratorKey != nullptr) { + index->DestroyKey(subQueryExecState->m_endIteratorKey); + } + } + + subQueryExecState->m_searchKey = nullptr; + subQueryExecState->m_endIteratorKey = nullptr; + + if (table != nullptr) { + table->Unlock(); + } +} + +static void DestroyQueryExecState(JitQueryContext* jitContext, bool isDropCachedPlan = false) +{ + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; + if (execState == nullptr) { + return; + } + + if ((jitContext->m_subQueryContext != nullptr) && (execState->m_subQueryExecState)) { + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + CleanupSubQueryExecState(subQueryContext, &execState->m_subQueryExecState[i], isDropCachedPlan); + } + } + + CleanupExecStatePrimary(jitContext, execState, isDropCachedPlan); + + CleanupExecStateInner(jitContext, execState, isDropCachedPlan); + + // cleanup bitmap set + if (execState->m_bitmapSet != nullptr) { + MOT::MemSessionFree(execState->m_bitmapSet->GetData()); + execState->m_bitmapSet->MOT::BitmapSet::~BitmapSet(); + MOT::MemSessionFree(execState->m_bitmapSet); + execState->m_bitmapSet = nullptr; + } + + // cleanup aggregate array + if (execState->m_aggExecState != nullptr) { + MOT::MemSessionFree(execState->m_aggExecState); + execState->m_aggExecState = nullptr; + } + + // cleanup sub-query array + if (execState->m_subQueryExecState != nullptr) { + MOT::MemSessionFree(execState->m_subQueryExecState); + execState->m_subQueryExecState = nullptr; + } + + // cleanup invoke parameters + if (execState->m_invokeParams != nullptr) { + MOT_ASSERT(!IsJitContextUsageGlobal(jitContext->m_usage)); + JitMemFree(execState->m_invokeParams, jitContext->m_usage); + execState->m_invokeParams = nullptr; + } + + if (execState->m_nonNativeSortExecState != nullptr) { + DestroyJitNonNativeSortExecState(execState->m_nonNativeSortExecState, jitContext->m_usage); + execState->m_nonNativeSortExecState = nullptr; + } + + DestroyJitExecState(execState); + jitContext->m_execState = nullptr; + JitStatisticsProvider::GetInstance().AddSessionBytes((int64_t)sizeof(JitQueryExecState)); +} + +static void DestroyJitQueryContext(JitQueryContext* jitContext, bool isDropCachedPlan /* = false */) +{ + // cleanup execution state + DestroyQueryExecState(jitContext, isDropCachedPlan); + + // cleanup sub-query data array + CleanupJitSubQueryContextArray(jitContext, isDropCachedPlan); + + // cleanup keys(s) + CleanupJitContextPrimary(jitContext, isDropCachedPlan); + + // cleanup JOIN keys(s) + CleanupJitContextInner(jitContext, isDropCachedPlan); + + // cleanup parameter info array + if (jitContext->m_invokeParamInfo != nullptr) { + JitMemFree(jitContext->m_invokeParamInfo, jitContext->m_usage); + jitContext->m_invokeParamInfo = nullptr; + } + + // cleanup invoke context + if (jitContext->m_invokeContext != nullptr) { + DestroyJitContext(jitContext->m_invokeContext, isDropCachedPlan); + jitContext->m_invokeContext = nullptr; + } + + if (jitContext->m_nonNativeSortParams) { + DestroyJitNonNativeSortParams(jitContext->m_nonNativeSortParams, jitContext->m_usage); + jitContext->m_nonNativeSortParams = nullptr; + } +} + +static void DestroyCallSite(JitCallSite* callSite, JitContextUsage usage, bool isDropCachedPlan = false) +{ + if (callSite->m_queryContext != nullptr) { + DestroyJitContext(callSite->m_queryContext, isDropCachedPlan); + callSite->m_queryContext = nullptr; + } + + if (callSite->m_queryString != nullptr) { + JitMemFree(callSite->m_queryString, usage); + callSite->m_queryString = nullptr; + } + + if (callSite->m_callParamInfo != nullptr) { + JitMemFree(callSite->m_callParamInfo, usage); + callSite->m_callParamInfo = nullptr; + } + + if (callSite->m_tupDesc != nullptr) { + FreeTupleDesc(callSite->m_tupDesc); + callSite->m_tupDesc = nullptr; + } +} + +static void DestroyJitInvokeExecState(JitInvokedQueryExecState* execState) +{ + if (execState->m_workSlot != nullptr) { + execState->m_workSlot->tts_tupleDescriptor = nullptr; + ExecDropSingleTupleTableSlot(execState->m_workSlot); + execState->m_workSlot = nullptr; + } + if (execState->m_resultSlot != nullptr) { + execState->m_resultSlot->tts_tupleDescriptor = nullptr; + ExecDropSingleTupleTableSlot(execState->m_resultSlot); + execState->m_resultSlot = nullptr; + } + if (execState->m_params != nullptr) { + MOT::MemSessionFree(execState->m_params); + execState->m_params = nullptr; + } + if (execState->m_plan != nullptr) { + (void)SPI_freeplan(execState->m_plan); + execState->m_plan = nullptr; + } + if (execState->m_resultTypes != nullptr) { + MOT::MemSessionFree(execState->m_resultTypes); + execState->m_resultTypes = nullptr; + } + if (execState->m_expr != nullptr) { + execState->m_expr->func = nullptr; + execState->m_expr = nullptr; + } +} + +static void DestroyFunctionExecState(JitFunctionContext* jitContext) +{ + JitFunctionExecState* execState = (JitFunctionExecState*)jitContext->m_execState; + if (execState != nullptr) { + // release compiled function + if (execState->m_function != nullptr) { + MOT_ASSERT(execState->m_function->use_count > 0); + --execState->m_function->use_count; + MOT_LOG_TRACE("DestroyFunctionExecState(): Decreased use count of function %p to %lu: %s", + execState->m_function, + execState->m_function->use_count, + jitContext->m_queryString); + execState->m_function = nullptr; + } + + // cleanup sub-query invoke ExecState + if (execState->m_invokedQueryExecState != nullptr) { + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + DestroyJitInvokeExecState(&execState->m_invokedQueryExecState[i]); + } + MOT::MemSessionFree(execState->m_invokedQueryExecState); + execState->m_invokedQueryExecState = nullptr; + } + + // cleanup common attributes + DestroyJitExecState(execState); + jitContext->m_execState = nullptr; + JitStatisticsProvider::GetInstance().AddSessionBytes((int64_t)sizeof(JitFunctionExecState)); + } +} + +static void DestroyJitFunctionContext(JitFunctionContext* jitContext, bool isDropCachedPlan /* = false */) +{ + // cleanup execution state + DestroyFunctionExecState(jitContext); + + // cleanup sub-query data and tuple descriptors + if (jitContext->m_SPSubQueryList != nullptr) { + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + DestroyCallSite(callSite, jitContext->m_usage, isDropCachedPlan); + } + + JitMemFree(jitContext->m_SPSubQueryList, jitContext->m_usage); + } + + // cleanup type list + if (jitContext->m_paramTypes != nullptr) { + JitMemFree(jitContext->m_paramTypes, jitContext->m_usage); + jitContext->m_paramTypes = nullptr; + } + + // cleanup result tuple descriptor + if (jitContext->m_resultTupDesc != nullptr) { + FreeTupleDesc(jitContext->m_resultTupDesc); + jitContext->m_resultTupDesc = nullptr; + } + if (jitContext->m_rowTupDesc) { + FreeTupleDesc(jitContext->m_rowTupDesc); + jitContext->m_rowTupDesc = nullptr; + } +} + +static bool JitQueryContextRefersRelation(JitQueryContext* jitContext, uint64_t relationId) +{ + bool result = false; + if (jitContext->m_tableId == relationId) { + result = true; + } else if (jitContext->m_indexId == relationId) { + result = true; + } else if (jitContext->m_innerTableId == relationId) { + result = true; + } else if (jitContext->m_innerIndexId == relationId) { + result = true; + } else if (jitContext->m_subQueryCount > 0) { + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + if (jitContext->m_subQueryContext[i].m_tableId == relationId) { + result = true; + break; + } + if (jitContext->m_subQueryContext[i].m_indexId == relationId) { + result = true; + break; + } + } + } else if ((jitContext->m_commandType == JIT_COMMAND_INVOKE) && (jitContext->m_invokeContext != nullptr)) { + result = JitFunctionContextRefersRelation(jitContext->m_invokeContext, relationId); + } + + return result; +} + +static bool JitFunctionContextRefersRelation(JitFunctionContext* jitContext, uint64_t relationId) +{ + bool result = false; + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if ((callSite->m_queryContext != nullptr) && + JitQueryContextRefersRelation((JitQueryContext*)callSite->m_queryContext, relationId)) { + result = true; + break; + } + // non-jittable sub-queries are irrelevant in this context + } + return result; +} + +extern bool JitContextRefersRelation(MotJitContext* jitContext, uint64_t relationId) +{ + bool result = false; + if (jitContext != nullptr) { + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = JitQueryContextRefersRelation((JitQueryContext*)jitContext, relationId); + } else { + result = JitFunctionContextRefersRelation((JitFunctionContext*)jitContext, relationId); + } + } + return result; +} + +extern void PurgeJitContext(MotJitContext* jitContext, uint64_t relationId) { if (jitContext != nullptr) { #ifdef MOT_JIT_DEBUG - MOT_LOG_TRACE("Destroying JIT context %p with %" PRIu64 " executions of query: %s", + MOT_LOG_TRACE("Purging %s JIT %s context %p by external table %" PRIu64 " with %" PRIu64 + " executions of query: %s", + JitContextUsageToString(jitContext->m_usage), + JitContextTypeToString(jitContext->m_contextType), jitContext, - jitContext->m_execCount, + relationId, + jitContext->m_execState ? jitContext->m_execState->m_execCount : 0, jitContext->m_queryString); #else - MOT_LOG_TRACE("Destroying %s JIT context %p of query: %s", - jitContext->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", + MOT_LOG_TRACE("Purging %s JIT %s context %p by external table %" PRIu64 " of query: %s", + JitContextUsageToString(jitContext->m_usage), + JitContextTypeToString(jitContext->m_contextType), jitContext, + relationId, jitContext->m_queryString); #endif - // remove from JIT source - if (jitContext->m_jitSource != nullptr) { - RemoveJitSourceContext(jitContext->m_jitSource, jitContext); - jitContext->m_jitSource = nullptr; + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + PurgeJitQueryContext((JitQueryContext*)jitContext, relationId); + } else { + PurgeJitFunctionContext((JitFunctionContext*)jitContext, relationId); + } + if (jitContext->m_execState != nullptr) { + MOT_ATOMIC_STORE(jitContext->m_execState->m_purged, true); + } + } +} + +extern void DeprecateJitContext(MotJitContext* jitContext) +{ + MOT_LOG_TRACE("Deprecating %s JIT context %p: %s", + JitContextUsageToString(jitContext->m_usage), + jitContext, + jitContext->m_queryString); + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + validState |= JIT_CONTEXT_DEPRECATE; + MOT_ATOMIC_STORE(jitContext->m_validState, validState); + + PropagateValidState(jitContext, 0, 0); +} + +extern bool IsJitContextValid(MotJitContext* jitContext) +{ + return (MOT_ATOMIC_LOAD(jitContext->m_validState) == JIT_CONTEXT_VALID); +} + +extern bool IsJitSubContext(MotJitContext* jitContext) +{ + return IsJitSubContextInline(jitContext); +} + +extern bool IsJitContextPendingCompile(MotJitContext* jitContext) +{ + return ((MOT_ATOMIC_LOAD(jitContext->m_validState) & JIT_CONTEXT_PENDING_COMPILE) == JIT_CONTEXT_PENDING_COMPILE); +} + +extern bool IsJitContextDoneCompile(MotJitContext* jitContext) +{ + return ((MOT_ATOMIC_LOAD(jitContext->m_validState) & JIT_CONTEXT_DONE_COMPILE) == JIT_CONTEXT_DONE_COMPILE); +} + +extern bool IsJitContextErrorCompile(MotJitContext* jitContext) +{ + return ((MOT_ATOMIC_LOAD(jitContext->m_validState) & JIT_CONTEXT_ERROR_COMPILE) == JIT_CONTEXT_ERROR_COMPILE); +} + +extern bool GetJitContextCompileState(MotJitContext* jitContext, bool* isPending, bool* isDone, bool* isError) +{ + bool result = false; + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + *isPending = ((validState & JIT_CONTEXT_PENDING_COMPILE) == JIT_CONTEXT_PENDING_COMPILE); + *isDone = ((validState & JIT_CONTEXT_DONE_COMPILE) == JIT_CONTEXT_DONE_COMPILE); + *isError = ((validState & JIT_CONTEXT_ERROR_COMPILE) == JIT_CONTEXT_ERROR_COMPILE); + if (*isPending || *isDone || *isError) { + result = true; + } + return result; +} + +inline const char* JitContextStateToString(JitContextState state) +{ + switch (state) { + case JIT_CONTEXT_STATE_INIT: + return "INIT"; + case JIT_CONTEXT_STATE_READY: + return "READY"; + case JIT_CONTEXT_STATE_PENDING: + return "PENDING"; + case JIT_CONTEXT_STATE_DONE: + return "DONE"; + case JIT_CONTEXT_STATE_ERROR: + return "ERROR"; + case JIT_CONTEXT_STATE_INVALID: + return "INVALID"; + case JIT_CONTEXT_STATE_FINAL: + return "FINAL"; + default: + return "N/A"; + } +} + +extern JitContextState GetJitContextState(MotJitContext* jitContext) +{ + JitContextState state = JIT_CONTEXT_STATE_INIT; + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + if (validState == JIT_CONTEXT_VALID) { + state = JIT_CONTEXT_STATE_READY; + } else if ((validState & JIT_CONTEXT_PENDING_COMPILE) == JIT_CONTEXT_PENDING_COMPILE) { + state = JIT_CONTEXT_STATE_PENDING; + } else if ((validState & JIT_CONTEXT_DONE_COMPILE) == JIT_CONTEXT_DONE_COMPILE) { + state = JIT_CONTEXT_STATE_DONE; + } else if ((validState & JIT_CONTEXT_ERROR_COMPILE) == JIT_CONTEXT_ERROR_COMPILE) { + state = JIT_CONTEXT_STATE_ERROR; + } else if ((validState & JIT_CONTEXT_INVALID) == JIT_CONTEXT_INVALID) { + state = JIT_CONTEXT_STATE_INVALID; + } else if ((validState & JIT_CONTEXT_CHILD_QUERY_INVALID) == JIT_CONTEXT_CHILD_QUERY_INVALID) { + state = JIT_CONTEXT_STATE_INVALID; + } else if ((validState & JIT_CONTEXT_CHILD_SP_INVALID) == JIT_CONTEXT_CHILD_SP_INVALID) { + state = JIT_CONTEXT_STATE_INVALID; + } else if ((validState & JIT_CONTEXT_RELATION_INVALID) == JIT_CONTEXT_RELATION_INVALID) { + state = JIT_CONTEXT_STATE_INVALID; + } else if ((validState & JIT_CONTEXT_DEPRECATE) == JIT_CONTEXT_DEPRECATE) { + state = JIT_CONTEXT_STATE_INVALID; + } + MOT_LOG_TRACE("JIT context %p state is %s (validState %u): %s", + jitContext, + JitContextStateToString(state), + validState, + jitContext->m_queryString); + MOT_ASSERT((state != JIT_CONTEXT_STATE_INIT) && (state != JIT_CONTEXT_STATE_FINAL)); + return state; +} + +inline void MarkJitContextPendingCompile(MotJitContext* jitContext) +{ + // set pending state, be careful not to overwrite done or error state + uint8_t currState = MOT_ATOMIC_LOAD(jitContext->m_validState); + if (((currState & JIT_CONTEXT_DONE_COMPILE) == 0) && ((currState & JIT_CONTEXT_ERROR_COMPILE) == 0)) { + uint8_t newState = currState; + newState &= ~JIT_CONTEXT_DONE_COMPILE; + newState &= ~JIT_CONTEXT_ERROR_COMPILE; + newState |= JIT_CONTEXT_PENDING_COMPILE; + // we don't care if we fail here, because done and error states take precedence over pending state + MOT_ATOMIC_CAS(jitContext->m_validState, currState, newState); + } +} + +extern void MarkJitContextDoneCompile(MotJitContext* jitContext) +{ + // reset pending state, and set done state (it is ok to overwrite pending state) + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + validState &= ~JIT_CONTEXT_PENDING_COMPILE; + validState &= ~JIT_CONTEXT_ERROR_COMPILE; + validState |= JIT_CONTEXT_DONE_COMPILE; + MOT_ATOMIC_STORE(jitContext->m_validState, validState); +} + +extern void MarkJitContextErrorCompile(MotJitContext* jitContext) +{ + // reset pending state, and set done state (it is ok to overwrite pending state) + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + validState &= ~JIT_CONTEXT_PENDING_COMPILE; + validState &= ~JIT_CONTEXT_DONE_COMPILE; + validState |= JIT_CONTEXT_ERROR_COMPILE; + MOT_ATOMIC_STORE(jitContext->m_validState, validState); +} + +extern void ResetJitContextCompileState(MotJitContext* jitContext) +{ + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + validState &= ~JIT_CONTEXT_PENDING_COMPILE; + validState &= ~JIT_CONTEXT_DONE_COMPILE; + validState &= ~JIT_CONTEXT_ERROR_COMPILE; + MOT_ATOMIC_STORE(jitContext->m_validState, validState); +} + +extern void ResetErrorState(JitExecState* execState) +{ + execState->m_errorMessage = PointerGetDatum(nullptr); + execState->m_errorDetail = PointerGetDatum(nullptr); + execState->m_errorHint = PointerGetDatum(nullptr); + execState->m_sqlStateString = PointerGetDatum(nullptr); + execState->m_sqlState = 0; + execState->m_nullColumnId = 0; +} + +static inline void ResetJitContextTable(MotJitContext* jitContext, uint64_t relationId) +{ + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + JitQueryContext* queryContext = (JitQueryContext*)jitContext; + if ((queryContext->m_table != nullptr) && (queryContext->m_tableId == relationId)) { + queryContext->m_table = nullptr; + } + if ((queryContext->m_innerTable != nullptr) && (queryContext->m_innerTableId == relationId)) { + queryContext->m_innerTable = nullptr; + } + } +} + +static void PropagateValidState(MotJitContext* jitContext, uint8_t invalidFlag, uint64_t relationId) +{ + MOT_LOG_TRACE("Propagating valid state of context %p: %s", jitContext, jitContext->m_queryString); + if (invalidFlag == 0) { + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + invalidFlag = JIT_CONTEXT_CHILD_QUERY_INVALID; + } else { + invalidFlag = JIT_CONTEXT_CHILD_SP_INVALID; + } + } + MOT_LOG_TRACE("Valid-state is: %s", JitContextValidStateToString(invalidFlag)); + + uint8_t newValidState = 0; + MotJitContext* parentContext = jitContext->m_parentContext; + while (parentContext != nullptr) { + MOT_LOG_TRACE("Invalidating %s parent JIT context %p by child %s: %s", + JitContextUsageToString(jitContext->m_usage), + parentContext, + (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) ? "query" : "SP", + parentContext->m_queryString); + newValidState = MOT_ATOMIC_LOAD(parentContext->m_validState) | invalidFlag; + MOT_ATOMIC_STORE(parentContext->m_validState, newValidState); + if (parentContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + if (relationId != 0) { + ResetJitContextTable(jitContext, relationId); + } + } + jitContext = parentContext; + parentContext = jitContext->m_parentContext; + } +} + +extern void InvalidateJitContext(MotJitContext* jitContext, uint64_t relationId, uint8_t invalidFlag /* = 0 */) +{ + // set this context as invalid and all ancestors as child-invalid + MOT_LOG_TRACE("Invalidating %s JIT context %p: %s", + JitContextUsageToString(jitContext->m_usage), + jitContext, + jitContext->m_queryString); + // ATTENTION: all compilation flags are reset here, as we want to attempt to revalidate anyway, but be careful not + // to reset deprecate flag + ResetJitContextCompileState(jitContext); + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + validState |= JIT_CONTEXT_INVALID; + MOT_ATOMIC_STORE(jitContext->m_validState, validState); + + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + // either table or index was dropped. index was already nullified during purge. if this is table drop then we + // should also nullify table pointer. + if (relationId != 0) { + ResetJitContextTable(jitContext, relationId); + } + } + + PropagateValidState(jitContext, invalidFlag, relationId); +} + +static bool ReplenishDatumArray(JitDatumArray* source, JitDatumArray* target, JitContextUsage usage, int depth) +{ + MOT_LOG_TRACE("%d: Replenishing datum array with %u elements", depth, source->m_datumCount); + + if (source->m_datumCount == 0) { + DestroyDatumArray(target, usage); + return true; + } + + if (source->m_datumCount == target->m_datumCount) { + // destroy existing target datum elements and reuse them + for (uint32_t i = 0; i < target->m_datumCount; ++i) { + JitDatum* targetDatum = &target->m_datums[i]; + if (!targetDatum->m_isNull && !IsPrimitiveType(targetDatum->m_type)) { + JitMemFree(DatumGetPointer(targetDatum->m_datum), usage); + } + targetDatum->m_isNull = true; + } + } else { + DestroyDatumArray(target, usage); + uint32_t allocSize = source->m_datumCount * sizeof(JitDatum); + target->m_datums = (JitDatum*)JitMemAlloc(allocSize, usage); + if (target->m_datums == nullptr) { + MOT_LOG_TRACE("Failed allocate %u bytes while replenishing datum array", allocSize); + return false; } - // cleanup constant datum array - DestroyDatumArray(&jitContext->m_constDatums, jitContext->m_usage); + target->m_datumCount = source->m_datumCount; + for (uint32_t i = 0; i < target->m_datumCount; ++i) { + JitDatum* targetDatum = &target->m_datums[i]; + targetDatum->m_isNull = true; + } + } - // cleanup sub-query data array - CleanupJitContextSubQueryDataArray(jitContext); + // clone all datum elements + for (uint32_t i = 0; i < source->m_datumCount; ++i) { + JitDatum* sourceDatum = &source->m_datums[i]; + JitDatum* targetDatum = &target->m_datums[i]; + if (!sourceDatum->m_isNull) { + if (IsPrimitiveType(sourceDatum->m_type)) { + targetDatum->m_datum = sourceDatum->m_datum; + } else { + if (!CloneDatum(sourceDatum->m_datum, sourceDatum->m_type, &targetDatum->m_datum, usage)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", "Failed to replenish datum array entry"); + DestroyDatumArray(target, usage); + return false; + } + } + } + targetDatum->m_isNull = sourceDatum->m_isNull; + targetDatum->m_type = sourceDatum->m_type; + } + + return true; +} + +static bool ReplenishInvokeParams(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth) +{ + MOT_LOG_TRACE("%d: Replenishing %u invoke parameters for SP: %s", + depth, + source->m_invokeParamCount, + source->m_invokeContext ? source->m_invokeContext->m_queryString : source->m_invokedQueryString); + if (source->m_invokeParamCount == 0) { + if (target->m_invokeParamCount > 0) { + JitMemFree(target->m_invokeParamInfo, usage); + } + target->m_invokeParamInfo = nullptr; + } else { + size_t allocSize = sizeof(JitParamInfo) * source->m_invokeParamCount; + if (target->m_invokeParamCount < source->m_invokeParamCount) { + void* buf = JitMemRealloc(target->m_invokeParamInfo, allocSize, MOT::MEM_REALLOC_COPY_ZERO, usage); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to reallocate %u bytes for %u invoke parameters", + (unsigned)allocSize, + (unsigned)source->m_invokeParamCount); + return false; + } + target->m_invokeParamInfo = (JitParamInfo*)buf; + } + + MOT_ASSERT(target->m_invokeParamInfo != nullptr); + errno_t erc = memcpy_s(target->m_invokeParamInfo, allocSize, source->m_invokeParamInfo, allocSize); + securec_check(erc, "\0", "\0"); + } + target->m_invokeParamCount = source->m_invokeParamCount; + return true; +} + +static bool ReplenishSubQueryArray(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth) +{ + MOT_LOG_TRACE( + "%d: Replenishing %u sub-query data items: %s", depth, source->m_subQueryCount, source->m_queryString); + size_t allocSize = sizeof(JitSubQueryContext) * source->m_subQueryCount; + JitMemFree(target->m_subQueryContext, usage); + target->m_subQueryContext = (JitSubQueryContext*)JitMemAllocAligned(allocSize, L1_CACHE_LINE, usage); + if (target->m_subQueryContext == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u sub-query data array in JIT context object", + (unsigned)allocSize, + (unsigned)source->m_subQueryCount); + return false; + } + target->m_subQueryCount = source->m_subQueryCount; + + for (uint32_t i = 0; i < source->m_subQueryCount; ++i) { + target->m_subQueryContext[i].m_commandType = source->m_subQueryContext[i].m_commandType; + target->m_subQueryContext[i].m_table = source->m_subQueryContext[i].m_table; + target->m_subQueryContext[i].m_tableId = source->m_subQueryContext[i].m_tableId; + target->m_subQueryContext[i].m_index = source->m_subQueryContext[i].m_index; + target->m_subQueryContext[i].m_indexId = source->m_subQueryContext[i].m_indexId; + } + return true; +} + +static bool ReplenishAggregateArray(JitQueryContext* source, JitQueryContext* target, JitContextUsage usage, int depth) +{ + size_t allocSize = sizeof(JitInvokedQueryExecState) * source->m_aggCount; + JitQueryExecState* queryExecState = (JitQueryExecState*)target->m_execState; + if (queryExecState != nullptr) { + void* buf = (JitAggExecState*)JitMemRealloc( + queryExecState->m_aggExecState, allocSize, MOT::MEM_REALLOC_COPY_ZERO, usage); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u aggregate execution state array in Query JIT context object", + allocSize, + (unsigned)source->m_aggCount); + return false; + } + queryExecState->m_aggExecState = (JitAggExecState*)buf; + } + return true; +} + +static bool ReplenishInvokeContext(JitQueryContext* source, JitQueryContext* target, int depth) +{ + JitContextUsage usage = target->m_usage; + if (target->m_invokedQueryString != nullptr) { + JitMemFree(target->m_invokedQueryString, usage); + target->m_invokedQueryString = nullptr; + } + if (source->m_commandType == JIT_COMMAND_INVOKE) { + if (source->m_invokeContext != nullptr) { + if (target->m_invokeContext == nullptr) { + target->m_invokeContext = (JitFunctionContext*)CloneJitContext(source->m_invokeContext, usage); + if (target->m_invokeContext == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone invocation context"); + return false; + } + } else { + MOT_LOG_TRACE("%d: Replenishing invoked stored procedure context: %s", + depth, + source->m_invokeContext->m_queryString); + if (!ReplenishJitContext(source->m_invokeContext, target->m_invokeContext, depth + 1)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to replenish invocation context"); + return false; + } + } + } else { // unjittable invoked stored procedure + // cleanup previous invoked context + if (target->m_invokeContext != nullptr) { + DestroyJitContext(target->m_invokeContext); + target->m_invokeContext = nullptr; + } + MOT_ASSERT(source->m_invokedQueryString); + target->m_invokedQueryString = DupString(source->m_invokedQueryString, usage); + if (target->m_invokedQueryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to clone invoked query string: %s", + source->m_invokedQueryString); + return false; + } + target->m_invokedFunctionOid = source->m_invokedFunctionOid; + target->m_invokedFunctionTxnId = source->m_invokedFunctionTxnId; + } + // in either case replenish parameters passed to invoked stored procedure + if (target->m_invokeContext != nullptr) { + target->m_invokeContext->m_parentContext = target; + } + if (!ReplenishInvokeParams(source, target, usage, depth)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to replenish invocation parameter array"); + return false; + } + } else { + if (target->m_invokeParamCount > 0) { + JitMemFree(target->m_invokeParamInfo, usage); + } + target->m_invokeParamInfo = nullptr; + target->m_invokeParamCount = 0; + } + + MOT_ASSERT((target->m_invokeParamCount == 0 && target->m_invokeParamInfo == nullptr) || + (target->m_invokeParamCount > 0 && target->m_invokeParamInfo != nullptr)); + return true; +} + +static bool ReplenishJitQueryContext(JitQueryContext* source, JitQueryContext* target, int depth) +{ + MOT_LOG_TRACE("%d: Replenishing JIT query context %p into %p (table=%p): %s", + depth, + source, + target, + target->m_table, + source->m_queryString); + + JitQueryContextSetTablesAndIndices(source, target); + target->m_aggCount = source->m_aggCount; + target->m_subQueryCount = source->m_subQueryCount; + + JitContextUsage usage = target->m_usage; + + if (!ReplenishInvokeContext(source, target, depth)) { + MOT_LOG_TRACE("Failed to replenish invoke context"); + return false; + } + + // replenish sub-query tuple descriptor array + if (source->m_subQueryCount > 0) { + if (!ReplenishSubQueryArray(source, target, usage, depth)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to replenish sub-query array"); + return false; + } + } else { + if (target->m_subQueryContext != nullptr) { + JitMemFree(target->m_subQueryContext, usage); + target->m_subQueryContext = nullptr; + } + target->m_subQueryCount = 0; + } + + // replenish aggregate array + if (source->m_aggCount > 0) { + if (!ReplenishAggregateArray(source, target, usage, depth)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to replenish aggregate array"); + return false; + } + } else { + JitExec::JitQueryExecState* targetExecState = (JitExec::JitQueryExecState*)target->m_execState; + if ((targetExecState != nullptr) && (targetExecState->m_aggExecState != nullptr)) { + JitMemFree(targetExecState->m_aggExecState, usage); + targetExecState->m_aggExecState = nullptr; + } + } + + // replenish non-native sort parameters + if (target->m_nonNativeSortParams != nullptr) { + DestroyJitNonNativeSortParams(target->m_nonNativeSortParams, target->m_usage); + target->m_nonNativeSortParams = nullptr; + } + + if (source->m_nonNativeSortParams) { + target->m_nonNativeSortParams = CloneJitNonNativeSortParams(source->m_nonNativeSortParams, usage); + if (target->m_nonNativeSortParams == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to replenish non-native sort params"); + return false; + } + } + + if (target->m_execState != nullptr) { + DestroyQueryExecState(target); + target->m_execState = nullptr; + + if (!AllocJitQueryExecState(target)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to allocate query execution state"); + return false; + } + } + + MOT_LOG_TRACE("%d: Replenished JIT query context %p into %p (table=%p, index=%p): %s", + depth, + source, + target, + target->m_table, + target->m_index, + source->m_queryString); + return true; +} + +static bool ReplenishParamListInfo(JitCallSite* target, JitCallSite* source, JitContextUsage usage, int depth) +{ + if (source->m_queryContext != nullptr) { + MOT_LOG_TRACE("%d: Replenishing param list info of size %d: %s", + depth, + source->m_callParamCount, + source->m_queryContext->m_queryString); + } else { + MOT_LOG_TRACE( + "%d: Replenishing param list info of size %d: %s", depth, source->m_callParamCount, source->m_queryString); + } + if (source->m_callParamCount == 0) { + if (target->m_callParamCount > 0) { + JitMemFree(target->m_callParamInfo, usage); + target->m_callParamInfo = nullptr; + } + } else { + size_t allocSize = sizeof(JitCallParamInfo) * source->m_callParamCount; + if (target->m_callParamCount < source->m_callParamCount) { + void* buf = JitMemRealloc(target->m_callParamInfo, allocSize, MOT::MEM_REALLOC_COPY_ZERO, usage); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to re-allocate %u bytes for sub-query parameter array of size %u in Function JIT context", + allocSize, + source->m_callParamCount); + return false; + } + target->m_callParamInfo = (JitCallParamInfo*)buf; + } + errno_t erc = memcpy_s(target->m_callParamInfo, allocSize, source->m_callParamInfo, allocSize); + securec_check(erc, "\0", "\0"); + } + target->m_callParamCount = source->m_callParamCount; + return true; +} + +static bool ResizeSubQueryArray(JitFunctionContext* source, JitFunctionContext* target) +{ + JitContextUsage usage = target->m_usage; + JitFunctionExecState* functionExecState = (JitFunctionExecState*)target->m_execState; + uint32_t origSubQueryCount = target->m_SPSubQueryCount; + uint64_t allocSize = sizeof(JitCallSite) * source->m_SPSubQueryCount; + + // resize sub-query array if needed (and the execution state array) + if (target->m_SPSubQueryCount < source->m_SPSubQueryCount) { + void* buf = JitMemRealloc(target->m_SPSubQueryList, allocSize, MOT::MEM_REALLOC_COPY_ZERO, usage); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %lu bytes for %u sub-query data array in Function JIT context object", + allocSize, + target->m_SPSubQueryCount); + return false; + } + target->m_SPSubQueryList = (JitCallSite*)buf; + target->m_SPSubQueryCount = source->m_SPSubQueryCount; + + if (functionExecState != nullptr) { + // the execution state must be totally destroyed, as the sub-queries might have totally changed + MOT_LOG_TRACE("Destroying all execution state objects (target query count is too small)"); + for (uint32_t i = 0; i < origSubQueryCount; ++i) { + JitInvokedQueryExecState* subExecState = &functionExecState->m_invokedQueryExecState[i]; + DestroyJitInvokeExecState(subExecState); + } + // now reallocate the entire array and zero it, so PrepareCallSite will create all missing members + allocSize = sizeof(JitInvokedQueryExecState) * source->m_SPSubQueryCount; + MOT_LOG_TRACE( + "Allocating %lu bytes for %u invoked query exec state objects", allocSize, source->m_SPSubQueryCount); + buf = JitMemRealloc(functionExecState->m_invokedQueryExecState, allocSize, MOT::MEM_REALLOC_ZERO, usage); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %lu bytes for %u sub-query exec state array in Function JIT context object", + allocSize, + target->m_SPSubQueryCount); + return false; + } + functionExecState->m_invokedQueryExecState = (JitInvokedQueryExecState*)buf; + MOT_LOG_TRACE("Invoke exec state array re-allocated at: %p (%lu bytes)", buf, allocSize); + } + } else { + // make sure then entire execution state is discarded and rebuilt from scratch + if (functionExecState != nullptr) { + // the execution state must be totally destroyed, as the sub-queries might have totally changed + MOT_LOG_TRACE("Destroying all execution state objects (target query count is large enough)"); + for (uint32_t i = 0; i < origSubQueryCount; ++i) { + JitInvokedQueryExecState* subExecState = &functionExecState->m_invokedQueryExecState[i]; + MOT_LOG_TRACE("Destroying sub-query %u invoke exec state at: %p", i, subExecState); + DestroyJitInvokeExecState(subExecState); + } + } + } + + return true; +} + +static bool ReplenishExistingSubQueries( + uint32_t replenishCount, JitFunctionContext* source, JitFunctionContext* target, int depth) +{ + MOT_LOG_TRACE("Replenishing (%u) existing sub-queries for JIT function context %p", replenishCount, target); + + JitContextUsage usage = target->m_usage; + for (uint32_t i = 0; i < replenishCount; ++i) { + JitCallSite* sourceCallSite = &source->m_SPSubQueryList[i]; + JitCallSite* targetCallSite = &target->m_SPSubQueryList[i]; + if (sourceCallSite->m_queryContext) { + MOT_LOG_TRACE("%d: Replenishing from jittable sub-query %u: %s", + depth, + i, + sourceCallSite->m_queryContext->m_queryString); + } else { + MOT_LOG_TRACE( + "%d: Replenishing from non-jittable sub-query %u: %s", depth, i, sourceCallSite->m_queryString); + } + if (targetCallSite->m_queryString != nullptr) { + JitMemFree(targetCallSite->m_queryString, target->m_usage); + targetCallSite->m_queryString = nullptr; + } + if (sourceCallSite->m_queryContext == nullptr) { + if (targetCallSite->m_queryContext != nullptr) { + DestroyJitContext(targetCallSite->m_queryContext); + targetCallSite->m_queryContext = nullptr; + } + targetCallSite->m_queryString = DupString(sourceCallSite->m_queryString, target->m_usage); + if (targetCallSite->m_queryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to clone call site query string: %s", + sourceCallSite->m_queryString); + return false; + } + MOT_LOG_TRACE("ReplenishJitFunctionContext(): Cloned string %p on %s scope into call site %p: %s", + targetCallSite->m_queryString, + IsJitContextUsageGlobal(target->m_usage) ? "global" : "local", + targetCallSite, + targetCallSite->m_queryString); + } else { + if (targetCallSite->m_queryContext == nullptr) { + // replenish from jittable sub-query into previously non-jittable sub-query + targetCallSite->m_queryContext = CloneJitContext(sourceCallSite->m_queryContext, usage); + if (targetCallSite->m_queryContext == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to replenish sub-query context %d for function JIT context (clone failed)", + i); + return false; + } + } else if (!ReplenishJitContext( + sourceCallSite->m_queryContext, targetCallSite->m_queryContext, depth + 1)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to replenish sub-query context %d for function JIT context", + i); + return false; + } + targetCallSite->m_queryContext->m_parentContext = target; + } + targetCallSite->m_queryCmdType = sourceCallSite->m_queryCmdType; + if (!ReplenishParamListInfo(targetCallSite, sourceCallSite, usage, depth)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to replenish sub-query %d parameters for function JIT context", + sourceCallSite->m_callParamCount); + return false; + } + + if (targetCallSite->m_tupDesc != nullptr) { + FreeTupleDesc(targetCallSite->m_tupDesc); + targetCallSite->m_tupDesc = nullptr; + } + if (sourceCallSite->m_tupDesc != nullptr) { + if (!CloneTupleDesc(sourceCallSite->m_tupDesc, &targetCallSite->m_tupDesc, usage)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to clone sub-query %d tuple descriptor for function JIT context", + i); + return false; + } + } + targetCallSite->m_exprIndex = sourceCallSite->m_exprIndex; + targetCallSite->m_isUnjittableInvoke = sourceCallSite->m_isUnjittableInvoke; + targetCallSite->m_isModStmt = sourceCallSite->m_isModStmt; + targetCallSite->m_isInto = sourceCallSite->m_isInto; + } + + return true; +} + +static bool ReplenishResultDescriptor(JitFunctionContext* source, JitFunctionContext* target) +{ + if (target->m_resultTupDesc != nullptr) { + FreeTupleDesc(target->m_resultTupDesc); + target->m_resultTupDesc = nullptr; + } + if (target->m_rowTupDesc != nullptr) { + FreeTupleDesc(target->m_rowTupDesc); + target->m_rowTupDesc = nullptr; + } + + if (!CloneResultDescriptors(source, target)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone result descriptors for function JIT context"); + return false; + } + + return true; +} + +static bool ReplenishParamTypes(JitFunctionContext* source, JitFunctionContext* target) +{ + JitContextUsage usage = target->m_usage; + if (target->m_paramTypes != nullptr) { + JitMemFree(target->m_paramTypes, usage); + target->m_paramTypes = nullptr; + } + + target->m_paramCount = source->m_paramCount; + if (source->m_paramCount > 0) { + uint64_t allocSize = sizeof(Oid) * target->m_paramCount; + target->m_paramTypes = (Oid*)JitMemAlloc(allocSize, usage); + if (target->m_paramTypes == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to allocate %u bytes for %u sub-query parameter array in Function JIT context object", + allocSize, + target->m_paramCount); + return false; + } + errno_t erc = memcpy_s(target->m_paramTypes, allocSize, source->m_paramTypes, allocSize); + securec_check(erc, "\0", "\0"); + } + + return true; +} + +static bool ReplenishJitFunctionContext(JitFunctionContext* source, JitFunctionContext* target, int depth) +{ + MOT_LOG_TRACE( + "%d: Replenishing JIT function context %p using %p: %s", depth, target, source, source->m_queryString); + JitContextUsage usage = target->m_usage; + target->m_functionOid = source->m_functionOid; + MOT_LOG_TRACE( + "Replenishing function txn id from %" PRIu64 " to %" PRIu64, target->m_functionTxnId, source->m_functionTxnId); + target->m_functionTxnId = source->m_functionTxnId; + target->m_SPArgCount = source->m_SPArgCount; + + // NOTE: in any case of failure caller will call destroy function for safe cleanup + + JitFunctionExecState* functionExecState = (JitFunctionExecState*)target->m_execState; + + // ATTENTION: Cache the original sub query count in the local variable, because target->m_SPSubQueryCount will be + // modified in ResizeSubQueryArray(). + uint32_t origSubQueryCount = target->m_SPSubQueryCount; + + // resize sub-query array if needed (and the execution state array) + if (!ResizeSubQueryArray(source, target)) { + MOT_LOG_TRACE( + "ReplenishJitFunctionContext(): Failed to resize sub-query array for JIT function context %p", target); + return false; + } + + // release compiled function + if ((functionExecState != nullptr) && (functionExecState->m_function != nullptr)) { + MOT_ASSERT(functionExecState->m_function->use_count > 0); + --functionExecState->m_function->use_count; + MOT_LOG_TRACE("ReplenishJitFunctionContext(): Decreased use count of function %p to %lu: %s", + functionExecState->m_function, + functionExecState->m_function->use_count, + target->m_queryString); + functionExecState->m_function = nullptr; + } + + // replenish existing sub-queries + uint32_t replenishCount = std::min(origSubQueryCount, source->m_SPSubQueryCount); + MOT_LOG_TRACE("Replenish count is: %u", replenishCount); + if (!ReplenishExistingSubQueries(replenishCount, source, target, depth)) { + MOT_LOG_TRACE("ReplenishJitFunctionContext(): Failed to replenish existing sub-queries (%u) for JIT function " + "context %p", + replenishCount, + target); + return false; + } + + // clone newly added sub-queries + for (uint32_t i = replenishCount; i < source->m_SPSubQueryCount; ++i) { + JitCallSite* sourceCallSite = &source->m_SPSubQueryList[i]; + JitCallSite* targetCallSite = &target->m_SPSubQueryList[i]; + if (sourceCallSite->m_queryContext) { + MOT_LOG_TRACE("%d: Cloning new sub-query %u: %s", depth, i, sourceCallSite->m_queryContext->m_queryString); + } else { + MOT_LOG_TRACE("%d: Cloning new sub-query %u: %s", depth, i, sourceCallSite->m_queryString); + } + if (!CloneCallSite(target, i, sourceCallSite, targetCallSite, usage)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Generate JIT Code", "Failed to clone call-site %d for function JIT context", i); + return false; + } + if (targetCallSite->m_queryContext != nullptr) { + targetCallSite->m_queryContext->m_parentContext = target; + } + } + + // release resource of unused sub-queries and their execution state objects + if (origSubQueryCount > source->m_SPSubQueryCount) { + for (uint32_t i = source->m_SPSubQueryCount; i < origSubQueryCount; ++i) { + JitCallSite* callSite = &target->m_SPSubQueryList[i]; + if (callSite->m_queryContext) { + MOT_LOG_TRACE( + "%d: Removing unused sub-query %u: %s", depth, i, callSite->m_queryContext->m_queryString); + } else { + MOT_LOG_TRACE("%d: Removing unused sub-query %u: %s", depth, i, callSite->m_queryString); + } + DestroyCallSite(callSite, target->m_usage); + + if ((functionExecState != nullptr) && (functionExecState->m_invokedQueryExecState != nullptr)) { + DestroyJitInvokeExecState(&functionExecState->m_invokedQueryExecState[i]); + } + } + } + target->m_SPSubQueryCount = source->m_SPSubQueryCount; + + // replenish result tuple descriptor (even though highly unexpected) + if (!ReplenishResultDescriptor(source, target)) { + MOT_LOG_TRACE( + "ReplenishJitFunctionContext(): Failed to clone result tuple desc for function JIT context %p", target); + return false; + } + + // replenish parameter types array + if (!ReplenishParamTypes(source, target)) { + MOT_LOG_TRACE("ReplenishJitFunctionContext(): Failed to replenish parameter types array for function JIT " + "context %p", + target); + return false; + } + + if (target->m_execState != nullptr) { + DestroyFunctionExecState(target); + target->m_execState = nullptr; + + if (!AllocJitFunctionExecState(target)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Generate JIT Code", "Failed to allocate function execution state"); + return false; + } + } + + MOT_LOG_TRACE("%d: Replenished JIT function context %p into %p: %s", depth, source, target, source->m_queryString); + return true; +} + +static bool ReplenishJitContext(MotJitContext* source, MotJitContext* target, int depth) +{ + if (source->m_contextType != target->m_contextType) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, + "JIT Compile", + "Cannot replenish %s JIT context %p from %s JIT context %p: mismatching context type", + JitContextTypeToString(target->m_contextType), + target, + JitContextTypeToString(source->m_contextType), + source); + return false; + } + const char* itemName = (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) ? "query" : "function"; + MOT_LOG_TRACE( + "%d: Replenishing JIT context %p from %p of %s: %s", depth, target, source, itemName, source->m_queryString); + + // no need to clone module/codegen object (they are safe in the source context) + JitContextUsage usage = target->m_usage; + target->m_llvmFunction = source->m_llvmFunction; + target->m_llvmSPFunction = source->m_llvmSPFunction; + target->m_commandType = source->m_commandType; + target->m_validState = source->m_validState; + target->m_queryString = source->m_queryString; + if (!ReplenishDatumArray(&source->m_constDatums, &target->m_constDatums, usage, depth)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", "Failed to clone constant datum array"); + return false; + } + + // pay attention: the JIT source/context might change! + if ((target->m_jitSource != source->m_jitSource) || (target->m_sourceJitContext != source)) { + MOT_LOG_TRACE("%d: Replenish target %p/%p from source %p/%p", + depth, + target->m_jitSource, + target->m_sourceJitContext, + source->m_jitSource, + source); + + // attention: even if source did not change we remove and add it again so that removal from deprecate source + // context takes place properly + PurgeJitContext(target, 0); // full cleanup before detaching from source + RemoveJitSourceContext(target->m_jitSource, target); + AddJitSourceContext(source->m_jitSource, target); + } + + bool result = true; + if (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = ReplenishJitQueryContext((JitQueryContext*)source, (JitQueryContext*)target, depth); + } else if (source->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + result = ReplenishJitFunctionContext((JitFunctionContext*)source, (JitFunctionContext*)target, depth); + } + + if (result) { + MOT_LOG_TRACE("%d: Replenished successfully JIT context %p from %p of %s: %s", + depth, + target, + source, + itemName, + source->m_queryString); + MOT_ATOMIC_STORE(target->m_validState, JIT_CONTEXT_VALID); + } else { + MOT_LOG_TRACE( + "Failed to replenish JIT context %p from %p of %s: %s", target, source, itemName, source->m_queryString); + } + + return result; +} + +static bool RevalidateJitQueryContext(MotJitContext* jitContext) +{ + MOT_LOG_TRACE("Re-validating JIT query context %p of query: %s", jitContext, jitContext->m_queryString); + if (jitContext->m_commandType != JIT_COMMAND_INVOKE) { + // this is a simple query, we declare revalidation fails to force destruction and re-creation + MOT_LOG_TRACE("Skipping revalidation of simple query to force code regeneration"); + return false; + } + + // we first make sure that the invoked function is valid (this is a secondary global context) + JitQueryContext* queryContext = (JitQueryContext*)jitContext; + MotJitContext* invokedContext = queryContext->m_invokeContext; + if (invokedContext == nullptr) { + MOT_LOG_TRACE("Skipping revalidation of invoke query into unjittable SP: %s", jitContext->m_queryString); + return true; // revalidation is OK + } + uint8_t validState = MOT_ATOMIC_LOAD(invokedContext->m_validState); + MOT_ASSERT(invokedContext->m_usage == JIT_CONTEXT_GLOBAL_SECONDARY); + MOT_ASSERT(invokedContext->m_isSourceContext == 0); + MOT_LOG_TRACE("Triggering revalidation of invoked context %p", invokedContext); + if (!RevalidateJitContext(invokedContext)) { + MOT_LOG_TRACE("Invoked context %p revalidation failed", invokedContext); + return false; + } + + // now if the function signature changed (e.g. default values changed), we need to re-generate code for the invoked + // query, we do so by dropping the query altogether and forcing code generation from scratch + if (validState & JIT_CONTEXT_INVALID) { // SP itself was replaced or dropped and recreated + MOT_LOG_TRACE( + "Forcing code regeneration from scratch for INVOKE of re-created SP: %s", jitContext->m_queryString); + return false; + } + + // otherwise the SP context was already replenished + MOT_ATOMIC_STORE(jitContext->m_validState, JIT_CONTEXT_VALID); + MOT_LOG_TRACE("Re-validated JIT query context %p of query: %s", jitContext, jitContext->m_queryString); + return true; +} + +static bool RevalidateJitFunctionContext(MotJitContext* jitContext) +{ + MOT_LOG_TRACE("Re-validating JIT function %p of function: %s", jitContext, jitContext->m_queryString); + // we arrive here when a sub-query or sub-SP was invalidated + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + if (validState & (JIT_CONTEXT_CHILD_QUERY_INVALID | JIT_CONTEXT_CHILD_SP_INVALID)) { + // now regenerate sub-queries and sub-SPs (sub-queries and sub-SPs are treated alike) + // we expect them to be cloned from the already regenerated source + MOT_LOG_TRACE("Regenerating code for SP sub-queries"); + if (!JitReCodegenFunctionQueries(jitContext)) { + MOT_LOG_TRACE("Failed to revalidate JIT SP sub-queries: %s", jitContext->m_queryString); + return false; + } + } + + MOT_LOG_TRACE("Re-validated JIT function context %p of query: %s", jitContext, jitContext->m_queryString); + return true; +} + +static bool ReattachDeprecateContext(MotJitContext* jitContext, TransactionId functionTxnId, const char*& queryString) +{ + // first check for deprecate source + uint8_t validState = MOT_ATOMIC_LOAD(jitContext->m_validState); + if (validState & JIT_CONTEXT_DEPRECATE) { + // find the latest matching source + MOT_LOG_TRACE("Revalidating deprecate source"); + bool shouldPopNamespace = false; + if ((jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + IsJitSubContextInline(jitContext) && + (jitContext->m_parentContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION)) { + // ATTENTION: this use case is ONLY when executing a jitted SP query, during lock-acquire via + // RevalidateCachedQuery() (see JitExecSubQuery() in jit_helpers.cpp) + JitFunctionContext* parentContext = (JitFunctionContext*)jitContext->m_parentContext; + // Try to get the function name to see if the procedure still exists. + char* funcName = get_func_name(parentContext->m_functionOid); + if (funcName == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_CONCURRENT_MODIFICATION, + "JIT Revalidate", + "Cannot attach to new source after deprecate: function %u concurrently dropped", + parentContext->m_functionOid); + // ATTENTION: we remain in deprecate state - the JIT SP query will be executed as non-jittable as + // many times as needed, until the calling SP execution finishes, and then on next round gets + // revalidated properly with this deprecate sub-query (see JitReCodegenFunctionQueries()) + return false; + } + if (!PushJitSourceNamespace(parentContext->m_functionOid, parentContext->m_queryString)) { + MOT_LOG_TRACE("JIT Revalidate: Failed to push JIT source namespace for stored procedure %s (%u)", + funcName, + parentContext->m_functionOid); + pfree_ext(funcName); + return false; + } + pfree_ext(funcName); + shouldPopNamespace = true; + } + + // NOTE: Once we remove the session-local JIT context from the JIT source, it is possible that the source + // be deprecated, freed and reused for completely different query. So removing the context from old source + // and adding it to the new source should be done atomically within the source map lock. Otherwise, it will + // result in a complete mess (JIT context getting attached to a completely wrong source). + LockJitSourceMap(); + JitSource* newSource = GetCachedJitSource(queryString); + if (shouldPopNamespace) { + PopJitSourceNamespace(); + } + if (newSource == nullptr) { + MOT_LOG_TRACE("Failed to find valid source"); + UnlockJitSourceMap(); + MarkJitContextErrorCompile(jitContext); // make sure we are in error state + return false; + } + + if (!IsSimpleQueryContext(jitContext) && IsPrematureRevalidation(newSource, functionTxnId)) { + MOT_LOG_TRACE("Skipping premature re-validation of JIT context %p with source %p, new source %p: %s", + jitContext, + jitContext->m_jitSource, + newSource, + queryString); + UnlockJitSourceMap(); + MarkJitContextErrorCompile(jitContext); // make sure we are in error state + return false; + } + + PurgeJitContext(jitContext, 0); // full cleanup before detaching from source + RemoveJitSourceContext(jitContext->m_jitSource, jitContext); + AddJitSourceContext(newSource, jitContext); + queryString = jitContext->m_jitSource->m_queryString; // get the query string again from new source + MOT_ATOMIC_STORE(jitContext->m_validState, GetJitSourceValidState(newSource)); + UnlockJitSourceMap(); + MOT_LOG_TRACE("Valid state updated to: %x", jitContext->m_validState); + } + + return true; +} + +static bool CleanupJitContextExecState(MotJitContext* jitContext) +{ + if (jitContext->m_execState != nullptr) { + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + DestroyQueryExecState((JitQueryContext*)jitContext); + jitContext->m_execState = nullptr; + + if (!AllocJitQueryExecState((JitQueryContext*)jitContext)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Jit Revalidate", "Failed to allocate query execution state"); + return false; + } + } else { + DestroyFunctionExecState((JitFunctionContext*)jitContext); + jitContext->m_execState = nullptr; + + if (!AllocJitFunctionExecState((JitFunctionContext*)jitContext)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Jit Revalidate", "Failed to allocate function execution state"); + return false; + } + } + } + + return true; +} + +extern bool RevalidateJitContext(MotJitContext* jitContext, TransactionId functionTxnId /* = InvalidTransactionId */) +{ + const char* queryString = jitContext->m_jitSource->m_queryString; + MOT_LOG_TRACE("Re-validating JIT context %p of query: %s", jitContext, queryString); + if (!jitContext->m_isSourceContext) { + // first check for deprecate source + // NOTE: queryString is passed by reference. If the deprecated context is getting attached to a new source, + // it will be updated accordingly. + if (!ReattachDeprecateContext(jitContext, functionTxnId, queryString)) { + MOT_LOG_TRACE("Failed to revalidate JIT context %p, couldn't attach to a new JIT source", jitContext); + return false; + } + + // Cleanup execution state before revalidation. It is possible that JIT context is not purged during create + // index as other DML operations can be executed concurrently during create index. So we must cleanup the old + // execution state before revalidation. Otherwise, we might end up with old keys/iterators in execution state, + // but new index pointers in JIT context. + if (!CleanupJitContextExecState(jitContext)) { + MOT_LOG_TRACE("Failed to revalidate JIT context %p, couldn't cleanup execution state", jitContext); + return false; + } + + // ATTENTION: At this point it is possible that some other session is concurrently compiling the same query. In + // this case, we don't really care, we just try to revalidate, but we only make sure not to overwrite "done" or + // "error" compile state with "pending" (so the race is resolved by flag precedence). + JitCodegenState codegenState = RevalidateJitSourceTxn(jitContext->m_jitSource, functionTxnId); + if (codegenState != JitCodegenState::JIT_CODEGEN_READY) { + bool pendingCompile = ((codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) || + (codegenState == JitCodegenState::JIT_CODEGEN_PENDING)); + if (pendingCompile) { + MOT_LOG_TRACE("Revalidate JIT context %p pending compilation with state %s: %s", + jitContext, + JitCodegenStateToString(codegenState), + queryString); + // the following call will not set pending state in case done/error was set (if compilation finished + // after we failed to revalidate, then we will catch that on the next invocation) + MarkJitContextPendingCompile(jitContext); + } else { + MOT_LOG_TRACE("Failed to revalidate JIT context %p, source re-validation failed with state %s: %s", + jitContext, + JitCodegenStateToString(codegenState), + queryString); + // we remain in code-gen error state, until some DDL comes and raises invalid flag, which will trigger + // another attempt to revalidate. We just make sure revalidate is not re-attempted by setting the valid + // state to compile-error (and also for making sure that OpFusion does not use JIT) + MarkJitContextErrorCompile(jitContext); + } + return false; + } + + // revalidate succeeded, so we clear all compilation flags + // we also want to avoid another attempt to revalidate - so we just set valid state to zero + // we hold query locks so there is no possible race with concurrent compilation after successful revalidate + MOT_LOG_TRACE("Revalidate JIT context %p source succeeded, now replenishing: %s", jitContext, queryString); + MOT_ATOMIC_STORE(jitContext->m_validState, JIT_CONTEXT_VALID); + + // although we should be protected by plan locks, we still prefer to guard access to source context + if (jitContext->m_jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + LockJitSource(jitContext->m_jitSource); + } + if (jitContext->m_jitSource->m_sourceJitContext == nullptr) { + MOT_LOG_TRACE("Failed to revalidate JIT context %p: concurrent update", jitContext); + if (jitContext->m_jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + UnlockJitSource(jitContext->m_jitSource); + } + MarkJitContextErrorCompile(jitContext); + return false; + } + if (!ReplenishJitContext(jitContext->m_jitSource->m_sourceJitContext, jitContext, 0)) { + MOT_LOG_TRACE("Failed to revalidate JIT context %p: replenish from source failed", jitContext); + if (jitContext->m_jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + UnlockJitSource(jitContext->m_jitSource); + } + MarkJitContextErrorCompile(jitContext); + return false; + } + if (jitContext->m_jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + UnlockJitSource(jitContext->m_jitSource); + } + + // we force prepare after replenish on top level session-local context (we do not prepare for execution + // secondary global context objects in JIT source objects) + if ((jitContext->m_usage == JIT_CONTEXT_LOCAL) && (jitContext->m_execState != nullptr)) { + MOT_ATOMIC_STORE(jitContext->m_execState->m_purged, true); + } + } else { + // revalidate source context + MOT_LOG_TRACE("Re-validating JIT source context %p of query: %s", jitContext, queryString); + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + if (!RevalidateJitQueryContext(jitContext)) { + MOT_LOG_TRACE("Failed to revalidate JIT query source context %p of query: %s", jitContext, queryString); + return false; + } + } else { + if (!RevalidateJitFunctionContext(jitContext)) { + MOT_LOG_TRACE( + "Failed to revalidate JIT function source context %p of function: %s", jitContext, queryString); + return false; + } + } + } + + MOT_ATOMIC_STORE(jitContext->m_validState, JIT_CONTEXT_VALID); + MOT_LOG_TRACE("Re-validated JIT context %p of query: %s", jitContext, queryString); + return true; +} + +extern const char* JitContextValidStateToString(uint8_t validState) +{ + // we disregard relation-invalid and deprecate bits + validState = validState & ~JIT_CONTEXT_RELATION_INVALID; + validState = validState & ~JIT_CONTEXT_DEPRECATE; + if (validState == JIT_CONTEXT_VALID) { + return "valid"; + } + if (validState == JIT_CONTEXT_INVALID) { + return "invalid"; + } + if (validState == JIT_CONTEXT_CHILD_QUERY_INVALID) { + return "sub-query invalid"; + } + if (validState == JIT_CONTEXT_CHILD_SP_INVALID) { + return "sub-SP invalid"; + } + if (validState == (JIT_CONTEXT_CHILD_QUERY_INVALID | JIT_CONTEXT_CHILD_SP_INVALID)) { + return "sub-query/SP invalid"; + } + if (validState == JIT_CONTEXT_PENDING_COMPILE) { + return "pending compile"; + } + if (validState == JIT_CONTEXT_DONE_COMPILE) { + return "done compile"; + } + if (validState == JIT_CONTEXT_ERROR_COMPILE) { + return "compile error"; + } + return "N/A"; +} + +static inline bool RefersRelation(JitQueryContext* jitContext, uint64_t relationId) +{ + if (((jitContext->m_table != nullptr) && (jitContext->m_tableId == relationId)) || + ((jitContext->m_index != nullptr) && (jitContext->m_indexId == relationId)) || + ((jitContext->m_innerTable != nullptr) && (jitContext->m_innerTableId == relationId)) || + ((jitContext->m_innerIndex != nullptr) && (jitContext->m_innerIndexId == relationId))) { + return true; + } + return false; +} + +static inline bool RefersRelation(JitSubQueryContext* subQueryContext, uint64_t relationId) +{ + if (((subQueryContext->m_table != nullptr) && (subQueryContext->m_tableId == relationId)) || + ((subQueryContext->m_index != nullptr) && (subQueryContext->m_indexId == relationId))) { + return true; + } + return false; +} + +static void PurgeJitQueryContext(JitQueryContext* jitContext, uint64_t relationId) +{ + bool refersRelation = false; + + if ((relationId == 0) || RefersRelation(jitContext, relationId)) { + refersRelation = true; + } + + if (!refersRelation) { + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + if (RefersRelation(subQueryContext, relationId)) { + refersRelation = true; + break; + } + } + } + + if (refersRelation) { + // If the relation is referred either in primary or inner or sub-query context, purge everything as the + // containing jit-source is marked as expired. + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; // cleanup keys(s) + MOT_LOG_TRACE("Purging JIT context %p primary keys by relation id %" PRIu64, jitContext, relationId); CleanupJitContextPrimary(jitContext); // cleanup JOIN keys(s) + MOT_LOG_TRACE("Purging JIT context %p inner keys by relation id %" PRIu64, jitContext, relationId); CleanupJitContextInner(jitContext); - // cleanup bitmap set - if (jitContext->m_bitmapSet != nullptr) { - MOT::MemSessionFree(jitContext->m_bitmapSet->GetData()); - jitContext->m_bitmapSet->MOT::BitmapSet::~BitmapSet(); - MOT::MemSessionFree(jitContext->m_bitmapSet); - jitContext->m_bitmapSet = nullptr; - } - - // cleanup code generator (only in global-usage) - if (jitContext->m_codeGen && (jitContext->m_usage == JIT_CONTEXT_GLOBAL)) { - FreeGsCodeGen(jitContext->m_codeGen); - jitContext->m_codeGen = nullptr; - } - - // cleanup null argument array - if (jitContext->m_argIsNull != nullptr) { - MOT::MemSessionFree(jitContext->m_argIsNull); - jitContext->m_argIsNull = nullptr; - } - - // cleanup TVM function (only in global-usage) - if (jitContext->m_tvmFunction && (jitContext->m_usage == JIT_CONTEXT_GLOBAL)) { - delete jitContext->m_tvmFunction; - jitContext->m_tvmFunction = nullptr; - } - - // cleanup TVM execution context - if (jitContext->m_execContext != nullptr) { - tvm::freeExecContext(jitContext->m_execContext); - jitContext->m_execContext = nullptr; - } - - FreeJitContext(jitContext); - } -} - -extern void PurgeJitContext(JitContext* jitContext, uint64_t relationId) -{ - if (jitContext != nullptr) { -#ifdef MOT_JIT_DEBUG - MOT_LOG_TRACE("Purging JIT context %p by external table %" PRIu64 " with %" PRIu64 " executions of query: %s", - jitContext, - relationId, - jitContext->m_execCount, - jitContext->m_queryString); -#else - MOT_LOG_TRACE("Purging %s JIT context %p by external table %" PRIu64 " of query: %s", - jitContext->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", - jitContext, - relationId, - jitContext->m_queryString); -#endif - - // cleanup keys(s) - if ((jitContext->m_table != nullptr) && (jitContext->m_table->GetTableExId() == relationId)) { - MOT_LOG_TRACE("Purging JIT context %p primary keys by relation id %" PRIu64, jitContext, relationId); - CleanupJitContextPrimary(jitContext); - } - - // cleanup JOIN keys(s) - if ((jitContext->m_innerTable != nullptr) && (jitContext->m_innerTable->GetTableExId() == relationId)) { - MOT_LOG_TRACE("Purging JIT context %p inner keys by relation id %" PRIu64, jitContext, relationId); - CleanupJitContextInner(jitContext); - } - // cleanup sub-query keys for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { - JitContext::SubQueryData* subQueryData = &jitContext->m_subQueryData[i]; - if ((subQueryData->m_table != nullptr) && (subQueryData->m_table->GetTableExId() == relationId)) { - MOT_LOG_TRACE( - "Purging sub-query %u data in JIT context %p by relation id %" PRIu64, i, jitContext, relationId); - CleanupJitContextSubQueryData(subQueryData); - } + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + MOT_LOG_TRACE( + "Purging sub-query %u data in JIT context %p by relation id %" PRIu64, i, jitContext, relationId); + CleanupJitSubQueryContext(subQueryContext, &execState->m_subQueryExecState[i]); + } + } + + // cleanup function context for INVOKE query + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + MOT_LOG_TRACE("Purging sub-function of JIT INVOKE context %p by relationId %" PRIu64, jitContext, relationId); + PurgeJitContext(jitContext->m_invokeContext, relationId); + } +} + +static void PurgeJitFunctionContext(JitFunctionContext* jitContext, uint64_t relationId) +{ + // purge JIT context in each sub-query call site + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (callSite->m_queryContext != nullptr) { + PurgeJitContext(callSite->m_queryContext, relationId); } } } -static void CleanupJitContextPrimary(JitContext* jitContext) +static void CleanupJitContextPrimary(JitQueryContext* jitContext, bool isDropCachedPlan /* = false */) { - if (jitContext->m_table) { - if (jitContext->m_index) { - if (jitContext->m_searchKey) { - jitContext->m_index->DestroyKey(jitContext->m_searchKey); - jitContext->m_searchKey = nullptr; - } + CleanupExecStatePrimary(jitContext, (JitQueryExecState*)jitContext->m_execState, isDropCachedPlan); + jitContext->m_index = nullptr; + jitContext->m_table = nullptr; +} - if (jitContext->m_endIteratorKey != nullptr) { - jitContext->m_index->DestroyKey(jitContext->m_endIteratorKey); - jitContext->m_endIteratorKey = nullptr; - } +static void CleanupJitContextInner(JitQueryContext* jitContext, bool isDropCachedPlan /* = false */) +{ + CleanupExecStateInner(jitContext, (JitQueryExecState*)jitContext->m_execState, isDropCachedPlan); + jitContext->m_innerIndex = nullptr; + jitContext->m_innerTable = nullptr; +} - if (jitContext->m_beginIterator != nullptr) { - destroyIterator(jitContext->m_beginIterator); - jitContext->m_beginIterator = nullptr; +static void CleanupJitSubQueryContextArray(JitQueryContext* jitContext, bool isDropCachedPlan /* = false */) +{ + if (jitContext->m_subQueryContext != nullptr) { + if (jitContext->m_execState != nullptr) { + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; + MOT_LOG_TRACE("Cleaning up sub-query data array in JIT context %p", jitContext); + if (execState->m_subQueryExecState != nullptr) { + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + MOT_LOG_TRACE("Cleaning up sub-query %u data in JIT context %p", i, jitContext); + CleanupJitSubQueryContext(subQueryContext, &execState->m_subQueryExecState[i]); + } + } else { + for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { + JitSubQueryContext* subQueryContext = &jitContext->m_subQueryContext[i]; + MOT_LOG_TRACE("Cleaning up sub-query %u data in JIT context %p", i, jitContext); + subQueryContext->m_index = nullptr; + subQueryContext->m_table = nullptr; + } } - - if (jitContext->m_endIterator != nullptr) { - destroyIterator(jitContext->m_endIterator); - jitContext->m_endIterator = nullptr; - } - - jitContext->m_index = nullptr; - } - - // cleanup JOIN outer row copy - if (jitContext->m_outerRowCopy != nullptr) { - jitContext->m_table->DestroyRow(jitContext->m_outerRowCopy); - jitContext->m_outerRowCopy = nullptr; } + JitMemFree(jitContext->m_subQueryContext, jitContext->m_usage); + jitContext->m_subQueryContext = nullptr; } } -static void CleanupJitContextInner(JitContext* jitContext) +static void CleanupJitSubQueryContext( + JitSubQueryContext* subQueryContext, JitSubQueryExecState* subQueryExecState, bool isDropCachedPlan /* = false */) { - if (jitContext->m_innerTable != nullptr) { - if (jitContext->m_innerIndex != nullptr) { - if (jitContext->m_innerSearchKey != nullptr) { - jitContext->m_innerIndex->DestroyKey(jitContext->m_innerSearchKey); - jitContext->m_innerSearchKey = nullptr; - } - - if (jitContext->m_innerEndIteratorKey != nullptr) { - jitContext->m_innerIndex->DestroyKey(jitContext->m_innerEndIteratorKey); - jitContext->m_innerEndIteratorKey = nullptr; - } - - if (jitContext->m_innerBeginIterator != nullptr) { - destroyIterator(jitContext->m_innerBeginIterator); - jitContext->m_innerBeginIterator = nullptr; - } - - if (jitContext->m_innerEndIterator != nullptr) { - destroyIterator(jitContext->m_innerEndIterator); - jitContext->m_innerEndIterator = nullptr; - } - - jitContext->m_innerIndex = nullptr; - } - } -} - -static void CleanupJitContextSubQueryDataArray(JitContext* jitContext) -{ - if (jitContext->m_subQueryData != nullptr) { - MOT_LOG_TRACE("Cleaning up sub-query data array in JIT context %p", jitContext); - for (uint32_t i = 0; i < jitContext->m_subQueryCount; ++i) { - JitContext::SubQueryData* subQueryData = &jitContext->m_subQueryData[i]; - MOT_LOG_TRACE("Cleaning up sub-query %u data in JIT context %p", i, jitContext); - CleanupJitContextSubQueryData(subQueryData); - } - MOT::MemGlobalFree(jitContext->m_subQueryData); - jitContext->m_subQueryData = nullptr; - } -} - -static void CleanupJitContextSubQueryData(JitContext::SubQueryData* subQueryData) -{ - MemoryContext oldCtx = CurrentMemoryContext; - CurrentMemoryContext = SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); - if (subQueryData->m_slot != nullptr) { - ExecDropSingleTupleTableSlot(subQueryData->m_slot); - subQueryData->m_slot = nullptr; - } - if (subQueryData->m_tupleDesc != nullptr) { - FreeTupleDesc(subQueryData->m_tupleDesc); - subQueryData->m_tupleDesc = nullptr; - } - if (subQueryData->m_index != nullptr) { - if (subQueryData->m_searchKey != nullptr) { - subQueryData->m_index->DestroyKey(subQueryData->m_searchKey); - subQueryData->m_searchKey = nullptr; - } - if (subQueryData->m_endIteratorKey != nullptr) { - subQueryData->m_index->DestroyKey(subQueryData->m_endIteratorKey); - subQueryData->m_endIteratorKey = nullptr; - } - subQueryData->m_index = nullptr; - } - CurrentMemoryContext = oldCtx; + CleanupSubQueryExecState(subQueryContext, subQueryExecState, isDropCachedPlan); + subQueryContext->m_index = nullptr; + subQueryContext->m_table = nullptr; } static JitContextPool* AllocSessionJitContextPool() @@ -814,11 +3755,13 @@ static JitContextPool* AllocSessionJitContextPool() extern void FreeSessionJitContextPool(JitContextPool* jitContextPool) { + MOT_LOG_TRACE( + "Destroy session-local JIT context pool, session context count: %u", u_sess->mot_cxt.jit_context_count); DestroyJitContextPool(jitContextPool); MOT::MemGlobalFree(jitContextPool); } -static MOT::Key* PrepareJitSearchKey(JitContext* jitContext, MOT::Index* index) +static MOT::Key* PrepareJitSearchKey(MotJitContext* jitContext, MOT::Index* index) { MOT::Key* key = index->CreateNewKey(); if (key == nullptr) { diff --git a/src/gausskernel/storage/mot/jit_exec/jit_context.h b/src/gausskernel/storage/mot/jit_exec/jit_context.h index c9b2e6613..7f0e68714 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_context.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_context.h @@ -27,12 +27,145 @@ #include "storage/mot/jit_def.h" #include "jit_llvm.h" -#include "jit_tvm.h" #include "mot_engine.h" +#include "executor/spi.h" +#include "utils/plpgsql.h" + +/*---------------------- JIT Context Valid Flags -------------------*/ +/** @define Denotes that the JIT context and all its descendants are valid. */ +#define JIT_CONTEXT_VALID 0x00 + +/** + * @define Denotes that the JIT context itself is invalid due to DDL. This flag exists for triggering revalidate + * attempt. + */ +#define JIT_CONTEXT_INVALID 0x01 + +/** @define Denotes that the JIT context itself is valid, but one of its descendant queries is invalid. */ +#define JIT_CONTEXT_CHILD_QUERY_INVALID 0x02 + +/** @define Denotes that the JIT context itself is valid, but one of its descendant sub-SPs is invalid. */ +#define JIT_CONTEXT_CHILD_SP_INVALID 0x04 + +/** @define Denotes that the JIT context is valid, but needs to re-fetch relation pointers. */ +#define JIT_CONTEXT_RELATION_INVALID 0x08 + +/** @define Denotes that the JIT context is deprecate, and needs reattachment to a valid source. */ +#define JIT_CONTEXT_DEPRECATE 0x10 + +/** @define Denotes that the JIT context is temporarily invalid, until its source finishes compilation. */ +#define JIT_CONTEXT_PENDING_COMPILE 0x20 + +/** + * @define Denotes that the JIT context finished compilation successfully. This is a transient state, until it goes + * through revalidation in the next query execution. + */ +#define JIT_CONTEXT_DONE_COMPILE 0x40 + +/** + * @define Denotes that the JIT context finished compilation with error. This is a transient state, until it goes + * through revalidation after a relevant DDL takes place. + */ +#define JIT_CONTEXT_ERROR_COMPILE 0x80 namespace JitExec { struct JitContextPool; struct JitSource; +struct MotJitContext; +struct JitFunctionContext; +struct JitNonNativeSortExecState; +struct JitNonNativeSortParams; + +/** @struct The parameter passing mode into a stored procedure. */ +enum class JitParamMode { + /** @var Denotes the parameter is being passed directly as-is into the store procedure. */ + JIT_PARAM_DIRECT, + + /** @var Denotes the parameter is copied into the parameter list of the store procedure. */ + JIT_PARAM_COPY +}; + +/** @struct The execution state of a jitted query. */ +struct JitExecState { + /** @var Last expression evaluation is-null flag. */ + uint32_t m_exprIsNull; + + /** @var Last expression evaluation collation id. */ + uint32_t m_exprCollationId; + + /** @var The outer context from which this query or stored procedure was invoked. */ + MotJitContext* m_invokingContext; + + /*---------------------- LLVM exception handling execution state -------------------*/ + /** @var The current exception status (non-zero value denotes an exception is currently being handled). */ + uint64_t m_exceptionStatus; + + /** @var The current exception value (denotes the thrown value). */ + uint64_t m_exceptionValue; + + /** @var The run-time fault code.. */ + uint64_t m_faultCode; + + /** @var The jump buffer used to generate run-time fault and halt execution. */ + sigjmp_buf m_faultBuf; + + /*---------------------- Error Reporting -------------------*/ + /** @var Error reporting information. The NULL violation column identifier. */ + MOT::Table* m_nullViolationTable; + + /** @var Error message thrown from this function into the calling function. */ + Datum m_errorMessage; + + /** @var Error detail thrown from this function into the calling function. */ + Datum m_errorDetail; + + /** @var Error hint thrown from this function into the calling function. */ + Datum m_errorHint; + + /** @var SQL state thrown from this function into the calling function (string form). */ + Datum m_sqlStateString; + + /** @var SQL state thrown from this function into the calling function. */ + int m_sqlState; + + /** @var Error reporting information. The NULL violation column identifier. */ + int m_nullColumnId; + + /*---------------------- Maintenance -----------------------------*/ + /** @var Specifies whether the index objects in this execution state were reset to null after TRUNCATE TABLE. */ + volatile bool m_purged; + + /*---------------------- Debug execution state -------------------*/ +#ifdef MOT_JIT_DEBUG + /** @var The number of times this context was invoked for execution. */ + uint64_t m_execCount; +#endif +}; + +/** @enum JIT context type constants. */ +enum class JitContextType : uint8_t { + /** @var Invalid JIT context type. */ + JIT_CONTEXT_TYPE_INVALID, + + /** @var Query JIT context type. */ + JIT_CONTEXT_TYPE_QUERY, + + /** @var Stored Procedure JIT context type. */ + JIT_CONTEXT_TYPE_FUNCTION +}; + +inline const char* JitContextTypeToString(JitContextType contextType) +{ + switch (contextType) { + case JitContextType::JIT_CONTEXT_TYPE_QUERY: + return "Query"; + case JitContextType::JIT_CONTEXT_TYPE_FUNCTION: + return "Function"; + case JitContextType::JIT_CONTEXT_TYPE_INVALID: + default: + return "Invalid"; + } +} /** @struct Array of constant datum objects used in JIT execution. */ struct JitDatum { @@ -54,191 +187,623 @@ struct JitDatumArray { JitDatum* m_datums; }; -/** - * @typedef The context for executing a jitted function. - */ -struct JitContext { +/** @struct Params for non-native sort SELECT range queries. */ +struct JitNonNativeSortParams { + /** @var Plan node id. Always set to 0 */ + int plan_node_id; + + /** @var Number of sort-key columns. */ + int numCols; + + /** @var Indexes of columns in the target list. */ + AttrNumber* sortColIdx; + + /** @var OIDs of operators to sort them by. */ + Oid* sortOperators; + + /** @var OIDs of collations. */ + Oid* collations; + + /** @var NULLS FIRST/LAST directions. */ + bool* nullsFirst; + + /** @var If bounded (bound != 0), how many tuples are needed. */ + int64 bound; + + /** @var Current scan direction. */ + ScanDirection scanDir; +}; + +/** @struct The context for executing a jitted function. */ +struct MotJitContext { /*---------------------- Meta-data -------------------*/ /** @var The LLVM code generator for this query. */ - dorado::GsCodeGen* m_codeGen; // L1 offset 0 + dorado::GsCodeGen* m_codeGen; /** @var The LLVM jit-compiled code for executing a query. */ - JitFunc m_llvmFunction; // L1 offset 8 + JitQueryFunc m_llvmFunction; - /** @var The TVM jit-compiled code for executing a query (when LLVM is unavailable on some platform). */ - tvm::Function* m_tvmFunction; // L1 offset 16 + /** @var The LLVM jit-compiled code for executing a stored procedure. */ + JitSPFunc m_llvmSPFunction; /** @var The type of the executed query (insert/update/select/delete). */ - JitCommandType m_commandType; // L1 offset 24 + JitCommandType m_commandType; /** @var Specifies whether this is a global or session-local context. */ - JitContextUsage m_usage; // L1 offset 25 + JitContextUsage m_usage; - /*---------------------- Execution state -------------------*/ - /** @var Maximum number of slots in the null datum array. */ - uint16_t m_argCount; // L1 offset 26 (constant) + /** @var Specifies whether this is a query or stored-procedure context. */ + JitContextType m_contextType; - /** @var Error reporting information. The NULL violation column identifier. */ - int m_nullColumnId; // L1 offset 28 (reusable) + /** @var Specifies The validity state of the context. */ + volatile uint8_t m_validState; - /** @var Null datum management array. */ - int* m_argIsNull; // L1 offset 32 (reusable) + /** @var Specified whether this context resides in a @ref JitSource. */ + uint8_t m_isSourceContext; - /** @var The table on which the query is to be executed . */ - MOT::Table* m_table; // L1 offset 40 (constant) + /** @var Used for member alignment. */ + uint8_t m_padding[3]; - /** @var The index on which the query is to be executed . */ - MOT::Index* m_index; // L1 offset 48 (may vary during TRUNCATE TABLE) + /** @brief The identifier of the session to which the context object belongs (invalid for global context). */ + MOT::SessionId m_sessionId; + + /** @var The identifier of the pool to which this JIT context belongs. */ + uint32_t m_poolId; + + /** @var The identifier of the sub-pool to which this JIT context belongs. */ + uint16_t m_subPoolId; + + /** @var Used for member alignment. */ + uint8_t m_padding1[2]; + + /** @var The identifier of the context within the pool to which this JIT context belongs. */ + uint32_t m_contextId; + + MOT::RC m_rc; + + uint32_t m_paddingRc; + + /** @var The source query string (or function name in case of a stored procedure). */ + const char* m_queryString; + + /** @var The array of constant datum objects used by the jitted function (global copy for all contexts). */ + JitDatumArray m_constDatums; + + /** @var The query execution state. */ + JitExecState* m_execState; + + /*---------------------- Cleanup -------------------*/ + /** @var Chain all JIT contexts in the same thread for cleanup during session end. */ + struct MotJitContext* m_next; + + /** @var Chain all context objects related to the same source, for cleanup during relation modification. */ + MotJitContext* m_nextInSource; + + /** @var Chain all deprecate context objects related to the same source, for safe delayed cleanup. */ + MotJitContext* m_nextDeprecate; + + /** @var The source JIT context from which this context was cloned (null for source context).*/ + MotJitContext* m_sourceJitContext; + + /** @brief The number of sessions using this source JIT context (zero for session contexts). */ + volatile uint64_t m_useCount; + + /** @var The JIT source from which this context originated. */ + JitSource* m_jitSource; + + /** @var The parent of this context (valid only if this is a sub-context). */ + MotJitContext* m_parentContext; +}; + +/** @class A simple linked list of JIT context objects. */ +class JitContextList { +public: + /** @brief Default constructor. */ + JitContextList() : m_head(nullptr), m_tail(nullptr) + {} + + /** @brief Default destructor. */ + ~JitContextList() + { + m_head = nullptr; + m_tail = nullptr; + } + + /** @brief Pushes an element on back of list. */ + inline void PushBack(MotJitContext* jitContext) + { + if (m_tail == nullptr) { + m_head = m_tail = jitContext; + } else { + m_tail->m_next = jitContext; + m_tail = jitContext; + } + jitContext->m_next = nullptr; + } + + /** @brief Starts iterating over list. */ + inline MotJitContext* Begin() + { + return m_head; + } + + /** @brief Queries whether list is empty. */ + inline bool Empty() const + { + return (m_head == nullptr); + } + +private: + /** @var The list head element. */ + MotJitContext* m_head; + + /** @var The list tail element. */ + MotJitContext* m_tail; +}; + +/*---------------------- Compound Query Context -------------------*/ +/** @struct Sub-query context. */ +struct JitSubQueryContext { + /** @var The sub-query command type (point-select, aggregate range select or limit 1 range-select). */ + JitCommandType m_commandType; + + /** @var Align next member to 8-byte offset. */ + uint8_t m_padding[7]; + + /** @var The table used by the sub-query. */ + MOT::Table* m_table; + + /** @var The table identifier for re-fetching it after TRUNCATE TABLE. */ + uint64_t m_tableId; + + /** @var The index used by the sub-query. */ + MOT::Index* m_index; /** @var The index identifier for re-fetching it after TRUNCATE TABLE. */ - uint64_t m_indexId; // L1 offset 56 (constant) + uint64_t m_indexId; +}; +/** @struct Parameter information. */ +struct JitParamInfo { + /** @var Specified parameter passing mode (direct or copy). */ + JitParamMode m_mode; + + /** @var Specifies parameter index for direct parameter. */ + int m_index; +}; + +/** @struct The context for executing a jitted query. */ +struct JitQueryContext : public MotJitContext { + /** @var The table on which the query is to be executed. */ + MOT::Table* m_table; + + /** @var The table identifier for re-fetching it after TRUNCATE TABLE. */ + uint64_t m_tableId; + + /** @var The index on which the query is to be executed (may vary after TRUNCATE TABLE). */ + MOT::Index* m_index; + + /** @var The index identifier for re-fetching it after TRUNCATE TABLE. */ + uint64_t m_indexId; + + /** @var The table used for inner scan in JOIN queries. */ + MOT::Table* m_innerTable; + + /** @var The table identifier for re-fetching it after TRUNCATE TABLE. */ + uint64_t m_innerTableId; + + /** @var The index used for inner scan in JOIN queries (may vary after TRUNCATE TABLE). */ + MOT::Index* m_innerIndex; + + /** @var The index identifier for re-fetching it after TRUNCATE TABLE. */ + uint64_t m_innerIndexId; + + /** @var Number of aggregators in query. */ + uint64_t m_aggCount; + + /** @var Array of tuple descriptors for each sub-query. */ + JitSubQueryContext* m_subQueryContext; + + /** @var The number of sub-queries used. */ + uint32_t m_subQueryCount; + + /** @var Number of parameters passed to invoke the stored procedure (includes default parameters). */ + uint32_t m_invokeParamCount; + + /** @var Parameter access array denoting how parameters are passed to an invoked stored procedure. */ + JitParamInfo* m_invokeParamInfo; + + /** @var The context of the invoked stored procedure. */ + JitFunctionContext* m_invokeContext; + + /** @var The query string for the unjittable invoked function. */ + char* m_invokedQueryString; + + /** @var The invoked function defining transaction identifier in case of unjittable invoked function. */ + TransactionId m_invokedFunctionTxnId; + + /** @var The invoked function identifier in case of unjittable invoked function. */ + Oid m_invokedFunctionOid; + + /** @var Align next member to 8 bytes. */ + uint32_t m_paddingQC; + + /** @var Non-native sort params */ + JitNonNativeSortParams* m_nonNativeSortParams; +}; + +/** @enum Kind of parameter being passed to SP sub-query*/ +enum class JitCallParamKind : uint32_t { + /** @var Denotes the parameter being passed is an SP argument. */ + JIT_CALL_PARAM_ARG, + + /** @var Denotes the parameter being passed is an SP local variable. */ + JIT_CALL_PARAM_LOCAL, +}; + +/** @struct Contains information about calling a query from within a stored procedure. */ +struct JitCallParamInfo { + /** @brief The index of the parameter in the parameter array passed to the sub-query*/ + int m_paramIndex; + + /** @brief The parameter type. */ + Oid m_paramType; + + /** @brief The kind of parameter being passed to the SP sub-query. */ + JitCallParamKind m_paramKind; + + /** @brief The index of the parameter in the this SP (-1 if none). */ + int m_invokeArgIndex; + + /** @brief The index of the passed local variable in the datum array of this SP (-1 if none). */ + int m_invokeDatumIndex; +}; + +/** @struct The call-site used for invoking a jitted-query from a jitted stored procedure. */ +struct JitCallSite { + /** @ var The JIT context for executing the called query. */ + MotJitContext* m_queryContext; + + /** @var Holds the query string for non-jittable sub-queries. */ + char* m_queryString; + + /** @var Holds the command type for non-jittable sub-queries. */ + CmdType m_queryCmdType; + + /** @var Number of parameters used in calling the query from within the SP (includes SP local variables). */ + int m_callParamCount; + + /** @var Information requires to form the parameter list used to invoke the query. */ + JitCallParamInfo* m_callParamInfo; + + /** @var The global tuple descriptor used to prepare tuple table slot object for the sub-query result. */ + TupleDesc m_tupDesc; + + /** @var The parsed expression index according to visitation order during JIT planning. */ + int m_exprIndex; + + /** @var Denotes whether this is an unjittable invoke query. */ + uint8_t m_isUnjittableInvoke; + + /** @var Denotes whether this is a modifying statement. */ + uint8_t m_isModStmt; + + /** @var Denotes whether the query result should be put into local variable. */ + uint8_t m_isInto; + + /** @var Align structure size to multiple of 8 bytes. */ + uint8_t m_padding; +}; + +/** @struct The context for executing a jitted stored procedure. */ +struct JitFunctionContext : public MotJitContext { + /** @var The unique identifier of the function in pg_proc. */ + Oid m_functionOid; + + /** @var The number of arguments expected by the function (including default parameters). */ + uint32_t m_SPArgCount; + + /** @var The id of the transaction that created the compiled function. */ + TransactionId m_functionTxnId; + + /** @var The list of parameter types passed to each call site. */ + Oid* m_paramTypes; + + /** @var The number of parameters passed to each call site. */ + uint32_t m_paramCount; + + /** @var Align next member to 8 bytes. */ + uint8_t m_paddingFC[4]; + + /** @var Stored procedure call site array for all queries called by the stored procedures. */ + JitCallSite* m_SPSubQueryList; + + /** @var The number of queries called by the stored procedure. */ + uint32_t m_SPSubQueryCount; + + /** @var Specifies the function returns a composite result (record). */ + uint32_t m_compositeResult; + + /** @var The function result tuple descriptor (for returning composite type). */ + TupleDesc m_resultTupDesc; + + /** @var The result row tuple descriptor (for returning composite type). */ + TupleDesc m_rowTupDesc; +}; + +/** @struct Direct row access execution state. */ +struct JitDirectRowAccessExecState { + /** @var Specifies whether the current row is being directly accessed. */ + int m_isRowDirect; + + /** @var Specifies whether the direct row was deleted. */ + int m_rowDeleted; + + /** @var The cached values of the row columns. */ + Datum* m_rowColumns; + + /** @var The cached values of the row column nulls. */ + bool* m_rowColumnNulls; +}; + +/** @struct Sub-query execution state. */ +struct JitSubQueryExecState { + /** @var Tuple descriptor for the sub-query result. */ + TupleDesc m_tupleDesc; + + /** @var Sub-query execution result. */ + TupleTableSlot* m_slot; + + /** @var The search key used by the sub-query. */ + MOT::Key* m_searchKey; + + /** @var The search key used by a range select sub-query (whether aggregated or limited). */ + MOT::Key* m_endIteratorKey; +}; + +/** @struct Aggregator execution state. */ +struct JitAggExecState { + /** @var Aggregated value used for SUM/MAX/MIN/COUNT() operators. */ + Datum m_aggValue; + + /** @var Aggregated value used for SUM/MAX/MIN/COUNT() operators. */ + uint64_t m_aggValueIsNull; + + /** @var Aggregated array used for AVG() operators. */ + Datum m_avgArray; + + /** @var A set of distinct items. */ + void* m_distinctSet; +}; + +/** @struct The execution state of a jitted query. */ +struct JitQueryExecState : public JitExecState { + /*---------------------- Point Query Execution -------------------*/ /** * @var The key object used to search a row for update/select/delete. This object can be reused * in each query. */ - MOT::Key* m_searchKey; // L1 offset 0 (reusable) + MOT::Key* m_searchKey; /** @var bitmap set used for incremental redo (only during update) */ - MOT::BitmapSet* m_bitmapSet; // L1 offset 8 (reusable) + MOT::BitmapSet* m_bitmapSet; /** * @var The key object used to set the end iterator for range scans. This object can be reused * in each query. */ - MOT::Key* m_endIteratorKey; // L1 offset 16 (reusable) - - /** @var Execution context used for pseudo-LLVM. */ - tvm::ExecContext* m_execContext; // L1 offset 24 - - /** @var Chain all JIT contexts in the same thread for cleanup during session end. */ - struct JitContext* m_next; // L1 offset 32 - - /** @var The source query string. */ - const char* m_queryString; // L1 offset 40 (constant) - - /** @var The array of constant datum objects used by the jitted function (global copy for all contexts). */ - JitDatumArray m_constDatums; + MOT::Key* m_endIteratorKey; /*---------------------- Range Scan execution state -------------------*/ /** @var Begin iterator for range select (stateful execution). */ - MOT::IndexIterator* m_beginIterator; // L1 offset 48 + MOT::IndexIterator* m_beginIterator; /** @var End iterator for range select (stateful execution). */ - MOT::IndexIterator* m_endIterator; // L1 offset 56 + MOT::IndexIterator* m_endIterator; /** @var Row for range select (stateful execution). */ - MOT::Row* m_row; // L1 offset 0 + MOT::Row* m_row; /** @var Stateful counter for limited range scans. */ - uint32_t m_limitCounter; // L1 offset 8 + uint32_t m_limitCounter; /** @var Scan ended flag (stateful execution). */ - uint32_t m_scanEnded; // L1 offset 12 + uint32_t m_scanEnded; - /** @var Aggregated value used for SUM/MAX/MIN/COUNT() operators. */ - Datum m_aggValue; // L1 offset 16 + /** @var Aggregates execution state. */ + JitAggExecState* m_aggExecState; - /** @var Specifies no value was aggregated yet for MAX/MIN() operators. */ - uint64_t m_maxMinAggNull; // L1 offset 24 - - /** @var Aggregated array used for AVG() operators. */ - Datum m_avgArray; // L1 offset 32 - - /** @var A set of distinct items. */ - void* m_distinctSet; // L1 offset 40 + /** @var Non native sort execution state. */ + JitNonNativeSortExecState* m_nonNativeSortExecState; /*---------------------- JOIN execution state -------------------*/ - /** @var The table used for inner scan in JOIN queries. */ - MOT::Table* m_innerTable; // L1 offset 48 (constant) - - /** @var The index used for inner scan in JOIN queries. */ - MOT::Index* m_innerIndex; // L1 offset 56 (may vary during TRUNCATE TABLE) - - /** @var The index identifier for re-fetching it after TRUNCATE TABLE. */ - uint64_t m_innerIndexId; // L1 offset 0 (constant) - /** @var The key object used to search row or iterator in the inner scan in JOIN queries. */ - MOT::Key* m_innerSearchKey; // L1 offset 8 (reusable) + MOT::Key* m_innerSearchKey; /** @var The key object used to search end iterator in the inner scan in JOIN queries. */ - MOT::Key* m_innerEndIteratorKey; // L1 offset 16 (reusable) + MOT::Key* m_innerEndIteratorKey; /** @var Begin iterator for inner scan in JOIN queries (stateful execution). */ - MOT::IndexIterator* m_innerBeginIterator; // L1 offset 24 + MOT::IndexIterator* m_innerBeginIterator; /** @var End iterator for inner scan in JOIN queries (stateful execution). */ - MOT::IndexIterator* m_innerEndIterator; // L1 offset 32 + MOT::IndexIterator* m_innerEndIterator; /** @var Row for inner scan in JOIN queries (stateful execution). */ - MOT::Row* m_innerRow; // L1 offset 40 + MOT::Row* m_innerRow; /** @var Copy of the outer row (SILO overrides it in the inner scan). */ - MOT::Row* m_outerRowCopy; // L1 offset 48 + MOT::Row* m_outerRowCopy; /** @var Scan ended flag (stateful execution). */ - uint64_t m_innerScanEnded; // L1 offset 56 - - /*---------------------- Compound Query Execution -------------------*/ - struct SubQueryData { - /** @var The sub-query command type (point-select, aggregate range select or limit 1 range-select). */ - JitCommandType m_commandType; // L1 offset 0 - - /** @var Align next member to 8-byte offset. */ - uint8_t m_padding[7]; // L1 offset 1 - - /** @var Tuple descriptor for the sub-query result. */ - TupleDesc m_tupleDesc; // L1 offset 8 - - /** @var Sub-query execution result. */ - TupleTableSlot* m_slot; // L1 offset 16 - - /** @var The table used by the sub-query. */ - MOT::Table* m_table; // L1 offset 24 - - /** @var The index used by the sub-query. */ - MOT::Index* m_index; // L1 offset 32 - - /** @var The index identifier for re-fetching it after TRUNCATE TABLE. */ - uint64_t m_indexId; // L1 offset 40 - - /** @var The search key used by the sub-query. */ - MOT::Key* m_searchKey; // L1 offset 48 - - /** @var The search key used by a range select sub-query (whether aggregated or limited). */ - MOT::Key* m_endIteratorKey; // L1 offset 56 - }; + uint64_t m_innerScanEnded; /** @var Array of tuple descriptors for each sub-query. */ - SubQueryData* m_subQueryData; // L1 offset 0 + JitSubQueryExecState* m_subQueryExecState; /** @var The number of sub-queries used. */ - uint64_t m_subQueryCount; // L1 offset 8 + uint64_t m_subQueryCount; - /*---------------------- Cleanup -------------------*/ - /** @var Chain all context objects related to the same source, for cleanup during relation modification. */ - JitContext* m_nextInSource; // L1 offset 16 + /*---------------------- Store Procedure Invocation Execution -------------------*/ + /** @var Parameter array list used for invoking the stored procedure. */ + ParamListInfo m_invokeParams; - /** @var The JIT source from which this context originated. */ - JitSource* m_jitSource; // L1 offset 24 + /** @var Parameter array list passed directly from invoke context into the invoked stored procedure. */ + ParamListInfo m_directParams; + + /** @var Used for holding lock on invoke SP record during SP execution. */ + HeapTuple m_invokeTuple; + + /** @var The result slot used for invoking the stored procedure. */ + TupleTableSlot* m_invokeSlot; + + /** @var The result number of tuples processed used for invoking the stored procedure. */ + uint64_t* m_invokeTuplesProcessed; + + /** @var The result scan ended flag used for invoking the stored procedure. */ + int* m_invokeScanEnded; /*---------------------- Batch Execution -------------------*/ /** * @var The number of times (iterations) a single stateful query was invoked. Used for distinguishing query * boundaries in a stateful query execution when client uses batches. */ - uint64_t m_iterCount; // L1 offset 32 + uint64_t m_iterCount; /** @var The number of full query executions. */ - uint64_t m_queryCount; // L1 offset 40 - - /*---------------------- Debug execution state -------------------*/ - /** @var The number of times this context was invoked for execution. */ -#ifdef MOT_JIT_DEBUG - uint64_t m_execCount; // L1 offset 48 -#endif + uint64_t m_queryCount; }; +/** @struct Data required for the execution of invoked queries. */ +struct JitInvokedQueryExecState { + /** @var The result slot of the query (volatile working copy). */ + TupleTableSlot* m_workSlot; + + /** + * @var Parameter array to invoke the query. Only parameters used by the sub-query are filled in before invoking + * the sub-query. + */ + ParamListInfo m_params; + + /** @var number of tuples processed by query. */ + uint64_t m_tuplesProcessed; + + /** @var Specifies whether scan ended for invoked query. */ + int m_scanEnded; + + /** @var The SPI result of executing the non-jittable sub-query. */ + int m_spiResult; + + /** @var The result slot of the query. */ + TupleTableSlot* m_resultSlot; + + /** @var SPI plan pointer for jittable sub-query locks and non-jittable sub-query execution. */ + SPIPlanPtr m_plan; + + /** @var Expression stub for non-jittable sub-query revalidation. */ + PLpgSQL_expr* m_expr; + + /** @var Array for non-jittable sub-query execution. */ + PLpgSQL_type* m_resultTypes; +}; + +/** + * @struct The execution state of a jitted stored procedure. + * @note The execution state of a sub-query invoked from a stored procedure is NOT maintained in the function execution + * state, but rather in the execution state of each sub-query, that is pointed by its containing call site. + */ +struct JitFunctionExecState : public JitExecState { + /** @var The sub-query execution state array. */ + JitInvokedQueryExecState* m_invokedQueryExecState; + + /** @var The parameters with which the function was invoked. */ + ParamListInfo m_functionParams; + + /** @var The compiled PG function. */ + PLpgSQL_function* m_function; + + /** + * @var Execution state stub required for re-parse during sub-query revalidation (both jittble and non-jittable + * sub-query). + */ + PLpgSQL_execstate m_estate; + + /** @var The number of active open sub-transactions (each one is opened for each sub-statement-block). */ + uint32_t m_openSubTxCount; + + /** @var The origin of the currently thrown exception. */ + int m_exceptionOrigin; + + /** @var The currently being executed sub-query id. */ + int m_currentSubQueryId; + + /** @var The current block nesting level for SPI connection management. */ + int m_spiBlockId; + + /** @var Connection identifier of SPI for this function execution. */ + int m_spiConnectId[MOT_JIT_MAX_BLOCK_DEPTH]; + + /** @var Saved memory context objects for each entered nested sub-transaction. */ + MemoryContext m_savedContexts[MOT_JIT_MAX_BLOCK_DEPTH]; + + /** @var The identifier of each sub-transaction in a nested block with exception. */ + SubTransactionId m_subTxns[MOT_JIT_MAX_BLOCK_DEPTH]; + + /*---------------------- LLVM exception handling execution state -------------------*/ + /** @struct Single exception frame in the run-time exception stack for LLVM. */ + struct ExceptionFrame { + /** @var The jump buffer for the frame. */ + sigjmp_buf m_jmpBuf; + + /** @var The frame index (for debugging). */ + uint64_t m_frameIndex; + + /** @var The next exception frame. */ + ExceptionFrame* m_next; + }; + + /** @var The run-time exception stack used to implement simple exceptions in LLVM. */ + ExceptionFrame* m_exceptionStack; +}; + +/** @struct The execution state for non-native sort query. */ +struct JitNonNativeSortExecState { + /** @var Tuple sort state for non-native sort. */ + Tuplesortstate* m_tupleSort; +}; + +inline bool IsJitSubContextInline(MotJitContext* jitContext) +{ + return (jitContext->m_parentContext != nullptr); +} + +inline bool IsInvokeQueryContext(MotJitContext* jitContext) +{ + return ((jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitContext->m_commandType == JIT_COMMAND_INVOKE)); +} + +inline bool IsSimpleQueryContext(MotJitContext* jitContext) +{ + return ((jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitContext->m_commandType != JIT_COMMAND_INVOKE)); +} + +/** + * @brief Allocate JitNonNativeSortParams struct with all members. + * @param numCols Number of column that should be placed in order. + * @param usage Specify the usage scope of the context (global or session-local). + * @return Pointer to fully allocated JitNonNativeSortParams structure or NULL for failure. + */ +extern JitNonNativeSortParams* AllocJitNonNativeSortParams(int numCols, JitContextUsage usage); + +/** + * @brief Destroy Non native sort params structure. + * @param sortParams The non native sort params structure to destroy. + * @param usage Specify the usage scope of the cloned context (global or session-local). + */ +extern void DestroyJitNonNativeSortParams(JitNonNativeSortParams* sortParam, JitContextUsage usage); + /** * @brief Initializes the global JIT context pool. * @return True if initialization succeeded, otherwise false. @@ -251,29 +816,33 @@ extern void DestroyGlobalJitContextPool(); /** * @brief Allocates a JIT context for future execution. * @param usage Specify the usage scope of the context (global or session-local). + * @param type Specifies the context type (query or stored procedure). * @return The allocated JIT context, or NULL if allocation failed. */ -extern JitContext* AllocJitContext(JitContextUsage usage); - -/** - * @brief Returns a JIT context back to its source pool. - * @param jitContext The JIT context to free. - */ -extern void FreeJitContext(JitContext* jitContext); +extern MotJitContext* AllocJitContext(JitContextUsage usage, JitContextType type); /** * @brief Clones a JIT context. * @param sourceJitContext The context to clone. + * @param usage Specify the usage scope of the cloned context (global or session-local). * @return The cloned context or NULL if failed. */ -extern JitContext* CloneJitContext(JitContext* sourceJitContext); +extern MotJitContext* CloneJitContext(MotJitContext* sourceJitContext, JitContextUsage usage); /** - * @brief Re-fetches all index objects in a JIT context. - * @param jitContext The JIT context for which index objects should be re-fetched. + * @brief Create JIT sort exec state from JitNonNativeSortParams structure. + * @param src The JitNonNativeSortParams to clone. + * @param usage Specify the usage scope of the cloned context (global or session-local). + * @return The cloned JitSortExecState or NULL if failed. + */ +extern JitNonNativeSortParams* CloneJitNonNativeSortParams(JitNonNativeSortParams* src, JitContextUsage usage); + +/** + * @brief Re-fetches all table and index objects in a JIT context. + * @param jitContext The JIT context for which table and index objects should be re-fetched. * @return True if the operation succeeded, otherwise false. */ -extern bool ReFetchIndices(JitContext* jitContext); +extern bool RefetchTablesAndIndices(MotJitContext* jitContext); /** * @brief Prepares a JIT context object for execution. All internal members are allocated and @@ -281,15 +850,10 @@ extern bool ReFetchIndices(JitContext* jitContext); * @param jitContext The JIT context to prepare. * @return True if succeeded, otherwise false. Consult @ref mm_get_root_error() for further details. */ -extern bool PrepareJitContext(JitContext* jitContext); +extern bool PrepareJitContext(MotJitContext* jitContext); -/** - * @brief Destroys a JIT context produced by a previous call to JitCodegenQuery. - * @detail All internal resources associated with the context object are released, and the context - * is returned to its source context pool. - * @param jitContext The JIT context to destroy. - */ -extern void DestroyJitContext(JitContext* jitContext); +/** @brief Queries whether a JIT context refers to a relation. */ +extern bool JitContextRefersRelation(MotJitContext* jitContext, uint64_t relationId); /** * @brief Release all resources related to any table used by the JIT context object. @@ -303,7 +867,34 @@ extern void DestroyJitContext(JitContext* jitContext); * @param relationId The external identifier of the modified relation that triggered purge. * @see SetJitSourceExpired(). */ -extern void PurgeJitContext(JitContext* jitContext, uint64_t relationId); +extern void PurgeJitContext(MotJitContext* jitContext, uint64_t relationId); + +/** @brief Marks a JIT context as deprecate. */ +extern void DeprecateJitContext(MotJitContext* jitContext); + +/** @brief Marks recursively (from bottom to top) a JIT context as invalid. */ +extern void InvalidateJitContext(MotJitContext* jitContext, uint64_t relationId, uint8_t invalidFlag = 0); + +/** @brief Marks a JIT context as deprecate. */ +extern void DeprecateJitContext(MotJitContext* jitContext); + +/** @brief Trigger code-generation of any missing sub-query. */ +extern bool RevalidateJitContext(MotJitContext* jitContext, TransactionId functionTxnId = InvalidTransactionId); + +/** @brief Converts valid state flags to string. */ +extern const char* JitContextValidStateToString(uint8_t validState); + +/** @brief Mark context as done waiting for source compilation to finish (result is success). */ +extern void MarkJitContextDoneCompile(MotJitContext* jitContext); + +/** @brief Mark context as done waiting for source compilation to finish (result is error). */ +extern void MarkJitContextErrorCompile(MotJitContext* jitContext); + +/** @brief Set all context compile flags to zero. */ +extern void ResetJitContextCompileState(MotJitContext* jitContext); + +/** @brief Resets all error management members. */ +extern void ResetErrorState(JitExecState* execState); } // namespace JitExec #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_context_pool.cpp b/src/gausskernel/storage/mot/jit_exec/jit_context_pool.cpp index d2342c682..61b085962 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_context_pool.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_context_pool.cpp @@ -33,15 +33,223 @@ namespace JitExec { DECLARE_LOGGER(JitContextPool, JitExec) +/** @define The maximum size of a JIT context object. */ +union JitUnifiedContext { + JitQueryContext m_queryContext; + JitFunctionContext m_functionContext; +}; + +static uint32_t nextPoolId = 0; + +#define JitContextSize (((sizeof(JitUnifiedContext) + 1) / 8) * 8) + +#define TRACE_JIT_CONTEXT_SUB_POOL(op, usage) \ + MOT_LOG_DEBUG("%s JIT context pool head %p free-count " op " context: %u", \ + JitContextUsageToString(usage), \ + contextSubPool->m_freeContextList, \ + contextSubPool->m_freeContextCount) + +#define TRACE_JIT_CONTEXT_POOL(op) \ + MOT_LOG_DEBUG("%s JIT context pool head %p free-count " op " context: %u", \ + JitContextUsageToString(contextPool->m_usage), \ + contextPool->m_freeContextList, \ + contextPool->m_freeContextCount) + +inline bool LockJitContextPool(JitContextPool* contextPool) +{ + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + int res = pthread_spin_lock(&contextPool->m_lock); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE( + res, pthread_spin_lock, "Global JIT Context Allocation", "Failed to acquire spin lock for global pool"); + return false; + } + } + return true; +} + +inline void UnlockJitContextPool(JitContextPool* contextPool) +{ + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + int res = pthread_spin_unlock(&contextPool->m_lock); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE(res, + pthread_spin_unlock, + "Global JIT Context Allocation", + "Failed to release spin lock for global pool"); + // system is in undefined state, we expect to crash any time soon, but we continue anyway + } + } +} + +static bool InitJitContextSubPool( + JitContextSubPool* contextSubPool, JitContextUsage usage, uint32_t poolId, uint16_t subPoolId, uint32_t poolSize) +{ + MOT_ASSERT(contextSubPool->m_contextPool == nullptr); + contextSubPool->m_contextPool = nullptr; + contextSubPool->m_freeContextList = nullptr; + contextSubPool->m_freeContextCount = 0; + contextSubPool->m_subPoolId = subPoolId; + + // allocate pool + size_t allocSize = JitContextSize * poolSize; + if (usage == JIT_CONTEXT_GLOBAL) { + contextSubPool->m_contextPool = (MotJitContext*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); + } else { + contextSubPool->m_contextPool = (MotJitContext*)MOT::MemSessionAllocAligned(allocSize, L1_CACHE_LINE); + } + if (contextSubPool->m_contextPool == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Context Pool Initialization", + "Failed to allocate %u JIT context objects (64-byte aligned %u bytes) for %s JIT context sub-pool", + poolSize, + allocSize, + JitContextUsageToString(usage)); + return false; + } + errno_t erc = memset_s(contextSubPool->m_contextPool, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // fill the free list + for (uint32_t i = 0; i < poolSize; ++i) { + MotJitContext* jitContext = (MotJitContext*)(((char*)contextSubPool->m_contextPool) + JitContextSize * i); + jitContext->m_next = contextSubPool->m_freeContextList; + jitContext->m_poolId = poolId; + jitContext->m_subPoolId = subPoolId; + jitContext->m_contextId = i; + jitContext->m_usage = usage; + contextSubPool->m_freeContextList = jitContext; + } + contextSubPool->m_freeContextCount = poolSize; + +#ifdef MOT_JIT_DEBUG + // manage free ids for error catching + contextSubPool->m_freeContextIds = new (std::nothrow) int[poolSize]; + if (contextSubPool->m_freeContextIds != nullptr) { + for (uint32_t i = 0; i < poolSize; ++i) { + contextSubPool->m_freeContextIds[i] = 1; + } + } +#endif + + return true; +} + +static void DestroyJitContextSubPool(JitContextSubPool* contextSubPool, JitContextUsage usage) +{ + // guard against repeated calls + if (contextSubPool->m_contextPool == nullptr) { + return; + } + + MOT_LOG_TRACE("Destroy %s JIT sub-context pool %" PRIu16 " head %p free-count %u", + JitContextUsageToString(usage), + contextSubPool->m_subPoolId, + contextSubPool->m_freeContextList, + contextSubPool->m_freeContextCount); + + MOT_ASSERT(contextSubPool->m_freeContextCount == JIT_SUB_POOL_SIZE); + +#ifdef MOT_JIT_DEBUG + // cleanup debug ids + if (contextSubPool->m_freeContextIds != nullptr) { + delete[] contextSubPool->m_freeContextIds; + contextSubPool->m_freeContextIds = nullptr; + } +#endif + + // free context pool + if (usage == JIT_CONTEXT_GLOBAL) { + MOT::MemGlobalFree(contextSubPool->m_contextPool); + } else { + MOT::MemSessionFree(contextSubPool->m_contextPool); + } + contextSubPool->m_contextPool = nullptr; + + // reset all pool attributes + contextSubPool->m_freeContextList = nullptr; + contextSubPool->m_freeContextCount = 0; +} + +static MotJitContext* AllocSubPooledJitContext( + JitContextSubPool* contextSubPool, JitContextUsage usage, uint32_t poolId, uint16_t subPoolId) +{ + // allocate one context + TRACE_JIT_CONTEXT_SUB_POOL("before alloc", usage); + MotJitContext* result = contextSubPool->m_freeContextList; + if (contextSubPool->m_freeContextList != nullptr) { + contextSubPool->m_freeContextList = contextSubPool->m_freeContextList->m_next; + --contextSubPool->m_freeContextCount; +#ifdef MOT_JIT_DEBUG + if (result != nullptr) { + if (contextSubPool->m_freeContextIds != nullptr) { + contextSubPool->m_freeContextIds[result->m_contextId] = 0; + } + } +#endif + } + TRACE_JIT_CONTEXT_SUB_POOL("after alloc", usage); + + if (result == nullptr) { + MOT_LOG_DEBUG("Sub-pool is empty"); + } else { + MOT_ASSERT((result->m_usage == usage) || + ((result->m_usage == JIT_CONTEXT_GLOBAL_SECONDARY) && (usage == JIT_CONTEXT_GLOBAL))); + MOT_ASSERT(result->m_poolId == poolId); + + // prepare context for initial usage + uint32_t contextId = result->m_contextId; // save context id before memset + errno_t erc = memset_s(result, sizeof(JitUnifiedContext), 0, sizeof(JitUnifiedContext)); + securec_check(erc, "\0", "\0"); + result->m_usage = usage; + result->m_poolId = poolId; + result->m_subPoolId = subPoolId; + result->m_contextId = contextId; + result->m_next = nullptr; + MOT_LOG_TRACE("Allocated %s JIT context at %p", JitContextUsageToString(usage), result); + } + + return result; +} + +static void FreeSubPooledJitContext(JitContextSubPool* contextSubPool, MotJitContext* jitContext, JitContextUsage usage, + uint32_t poolId, uint16_t subPoolId, uint32_t poolSize) +{ + if (contextSubPool == nullptr) { + MOT_ASSERT(false); // this should not happen + return; + } + + MOT_ASSERT(contextSubPool->m_freeContextCount < poolSize); +#ifdef MOT_JIT_DEBUG + MOT_ASSERT((contextSubPool->m_freeContextIds == nullptr) || + (contextSubPool->m_freeContextIds[jitContext->m_contextId] == 0)); +#endif + + // free the context into the pool + TRACE_JIT_CONTEXT_SUB_POOL("before free", usage); + jitContext->m_next = contextSubPool->m_freeContextList; + contextSubPool->m_freeContextList = jitContext; + ++contextSubPool->m_freeContextCount; +#ifdef MOT_JIT_DEBUG + if (contextSubPool->m_freeContextIds != nullptr) { + contextSubPool->m_freeContextIds[jitContext->m_contextId] = 1; + } +#endif + TRACE_JIT_CONTEXT_SUB_POOL("after free", usage); +} + extern bool InitJitContextPool(JitContextPool* contextPool, JitContextUsage usage, uint32_t poolSize) { - MOT_ASSERT(contextPool->m_contextPool == nullptr); + // we align pool size to a multiple of sub-pool size + poolSize = ((poolSize + JIT_SUB_POOL_SIZE - 1) / JIT_SUB_POOL_SIZE) * JIT_SUB_POOL_SIZE; contextPool->m_usage = usage; - contextPool->m_contextPool = nullptr; - contextPool->m_poolSize = 0; - contextPool->m_freeContextList = nullptr; - contextPool->m_freeContextCount = 0; + contextPool->m_subPools = nullptr; + contextPool->m_poolSize = poolSize; + contextPool->m_subPoolCount = poolSize / JIT_SUB_POOL_SIZE; + contextPool->m_poolId = MOT_ATOMIC_INC(nextPoolId); + // allocate lock for global pool if (usage == JIT_CONTEXT_GLOBAL) { int res = pthread_spin_init(&contextPool->m_lock, 0); if (res != 0) { @@ -53,43 +261,60 @@ extern bool InitJitContextPool(JitContextPool* contextPool, JitContextUsage usag } } - size_t allocSize = sizeof(JitContext) * poolSize; - contextPool->m_contextPool = (JitContext*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); - if (contextPool->m_contextPool == nullptr) { + // allocate pool + size_t allocSize = sizeof(JitContextSubPool*) * poolSize; + if (usage == JIT_CONTEXT_GLOBAL) { + contextPool->m_subPools = (JitContextSubPool**)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); + } else { + contextPool->m_subPools = (JitContextSubPool**)MOT::MemSessionAllocAligned(allocSize, L1_CACHE_LINE); + } + if (contextPool->m_subPools == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Context Pool Initialization", - "Failed to allocate %u JIT context objects (64-byte aligned %u bytes) for %s JIT context pool", - poolSize, + "Failed to allocate %u bytes for JIT context sub-pool array with %u pools", allocSize, - usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local"); + poolSize, + JitContextUsageToString(usage)); if (usage == JIT_CONTEXT_GLOBAL) { - pthread_spin_destroy(&contextPool->m_lock); + (void)pthread_spin_destroy(&contextPool->m_lock); } return false; } - errno_t erc = memset_s(contextPool->m_contextPool, allocSize, 0, allocSize); + errno_t erc = memset_s(contextPool->m_subPools, allocSize, 0, allocSize); securec_check(erc, "\0", "\0"); - // fill the free list - contextPool->m_poolSize = poolSize; - for (uint32_t i = 0; i < poolSize; ++i) { - JitContext* jitContext = &contextPool->m_contextPool[i]; - jitContext->m_next = contextPool->m_freeContextList; - contextPool->m_freeContextList = jitContext; - } - contextPool->m_freeContextCount = contextPool->m_poolSize; - return true; } extern void DestroyJitContextPool(JitContextPool* contextPool) { - if (contextPool->m_contextPool == nullptr) { + // guard against repeated calls + if (contextPool->m_subPools == nullptr) { return; } - MOT::MemGlobalFree(contextPool->m_contextPool); + MOT_LOG_DEBUG("Destroy %s JIT context pool", JitContextUsageToString(contextPool->m_usage)); + // free sub-pools + for (uint16_t i = 0; i < contextPool->m_subPoolCount; ++i) { + if (contextPool->m_subPools[i] != nullptr) { + DestroyJitContextSubPool(contextPool->m_subPools[i], contextPool->m_usage); + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + MOT::MemGlobalFree(contextPool->m_subPools[i]); + } else { + MOT::MemSessionFree(contextPool->m_subPools[i]); + } + contextPool->m_subPools[i] = nullptr; + } + } + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + MOT::MemGlobalFree(contextPool->m_subPools); + } else { + MOT::MemSessionFree(contextPool->m_subPools); + } + contextPool->m_subPools = nullptr; + + // free global pool lock if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { int res = pthread_spin_destroy(&contextPool->m_lock); if (res != 0) { @@ -100,92 +325,135 @@ extern void DestroyJitContextPool(JitContextPool* contextPool) } } - contextPool->m_contextPool = nullptr; + // reset all pool attributes contextPool->m_poolSize = 0; - contextPool->m_freeContextList = nullptr; - contextPool->m_freeContextCount = 0; } -extern JitContext* AllocPooledJitContext(JitContextPool* contextPool) +extern MotJitContext* AllocPooledJitContext(JitContextPool* contextPool) { - if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { - int res = pthread_spin_lock(&contextPool->m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE( - res, pthread_spin_lock, "Global JIT Context Allocation", "Failed to acquire spin lock for global pool"); - return NULL; + // lock global pool if needed + if (!LockJitContextPool(contextPool)) { + return nullptr; + } + + // allocate one context from any pool + MotJitContext* result = nullptr; + for (uint16_t i = 0; i < contextPool->m_subPoolCount; ++i) { + if (contextPool->m_subPools[i] != nullptr) { + result = + AllocSubPooledJitContext(contextPool->m_subPools[i], contextPool->m_usage, contextPool->m_poolId, i); + if (result != nullptr) { + break; + } } } - MOT_LOG_TRACE("%s JIT context pool free-count before alloc context: %u", - contextPool->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", - contextPool->m_freeContextCount); - JitContext* result = contextPool->m_freeContextList; - if (contextPool->m_freeContextList != nullptr) { - contextPool->m_freeContextList = contextPool->m_freeContextList->m_next; - --contextPool->m_freeContextCount; - } - MOT_LOG_DEBUG("%s JIT context pool free-count after alloc context: %u", - contextPool->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", - contextPool->m_freeContextCount); + // see if new sub-pool allocation is required + if (result == nullptr) { + MOT_LOG_TRACE("All Sub-pools are empty"); + for (uint16_t i = 0; i < contextPool->m_subPoolCount; ++i) { + if (contextPool->m_subPools[i] == nullptr) { + size_t allocSize = sizeof(JitContextSubPool); + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + contextPool->m_subPools[i] = + (JitContextSubPool*)MOT::MemGlobalAllocAligned(allocSize, L1_CACHE_LINE); + } else { + contextPool->m_subPools[i] = + (JitContextSubPool*)MOT::MemSessionAllocAligned(allocSize, L1_CACHE_LINE); + } + if (contextPool->m_subPools[i] == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Context Pool Initialization", + "Failed to allocate %u bytes for JIT context sub-pool", + allocSize); + break; + } + errno_t erc = memset_s(contextPool->m_subPools[i], allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); - if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { - int res = pthread_spin_unlock(&contextPool->m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(res, - pthread_spin_unlock, - "Global JIT Context Allocation", - "Failed to release spin lock for global pool"); - // system is in undefined state, we expect to crash any time soon, but we continue anyway + if (!InitJitContextSubPool(contextPool->m_subPools[i], + contextPool->m_usage, + contextPool->m_poolId, + i, + JIT_SUB_POOL_SIZE)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Context Pool Initialization", + "Failed to initialize JIT Context pool %u, sub-pool %u", + contextPool->m_poolId, + i); + break; + } + result = AllocSubPooledJitContext( + contextPool->m_subPools[i], contextPool->m_usage, contextPool->m_poolId, i); + if (result == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "JIT Context Pool Allocation", + "Failed to allocate JIT context from JIT Context pool %u, new sub-pool %u", + contextPool->m_poolId, + i); + } + break; + } } } + // unlock global pool if needed + UnlockJitContextPool(contextPool); + if (result == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "JIT Context Allocation", "Failed to allocate %s JIT context: pool with %u context objects depleted", - contextPool->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", + JitContextUsageToString(contextPool->m_usage), contextPool->m_poolSize); - } else { - errno_t erc = memset_s(result, sizeof(JitContext), 0, sizeof(JitContext)); - securec_check(erc, "\0", "\0"); - result->m_usage = contextPool->m_usage; } return result; } -extern void FreePooledJitContext(JitContextPool* contextPool, JitContext* jitContext) +extern void FreePooledJitContext(JitContextPool* contextPool, MotJitContext* jitContext) { - if (contextPool == nullptr) + if (contextPool == nullptr) { + MOT_ASSERT(false); // this should not happen return; - if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { - int res = pthread_spin_lock(&contextPool->m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(res, - pthread_spin_lock, - "Global JIT Context De-allocation", - "Failed to acquire spin lock for global pool"); - return; - } } - jitContext->m_next = contextPool->m_freeContextList; - contextPool->m_freeContextList = jitContext; - ++contextPool->m_freeContextCount; - MOT_LOG_TRACE("%s JIT context pool free-count after free context: %u", - contextPool->m_usage == JIT_CONTEXT_GLOBAL ? "global" : "session-local", - contextPool->m_freeContextCount); + MOT_ASSERT(jitContext->m_poolId == contextPool->m_poolId); + MOT_ASSERT((jitContext->m_usage == contextPool->m_usage) || + ((jitContext->m_usage == JIT_CONTEXT_GLOBAL_SECONDARY) && (contextPool->m_usage == JIT_CONTEXT_GLOBAL))); - if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { - int res = pthread_spin_unlock(&contextPool->m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(res, - pthread_spin_unlock, - "Global JIT Context De-allocation", - "Failed to release spin lock for global pool"); - // system is in undefined state, we expect to crash any time soon, but we continue anyway - } + MOT_LOG_TRACE("Freeing %s JIT %s context at %p", + JitContextUsageToString(contextPool->m_usage), + JitContextTypeToString(jitContext->m_contextType), + jitContext); + + // lock global pool if needed + if (!LockJitContextPool(contextPool)) { + return; } + + uint16_t subPoolId = jitContext->m_subPoolId; + JitContextSubPool* contextSubPool = contextPool->m_subPools[subPoolId]; + FreeSubPooledJitContext( + contextSubPool, jitContext, contextPool->m_usage, contextPool->m_poolId, subPoolId, JIT_SUB_POOL_SIZE); + + // release sub-pool if it became empty + if (contextSubPool->m_freeContextCount == JIT_SUB_POOL_SIZE) { + DestroyJitContextSubPool(contextSubPool, contextPool->m_usage); + if (contextPool->m_usage == JIT_CONTEXT_GLOBAL) { + MOT::MemGlobalFree(contextSubPool); + } else { + MOT::MemSessionFree(contextSubPool); + } + contextPool->m_subPools[subPoolId] = nullptr; + } + + // unlock global pool if needed + UnlockJitContextPool(contextPool); +} + +extern uint32_t GetJitContextSize() +{ + return (uint32_t)JitContextSize; } } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_context_pool.h b/src/gausskernel/storage/mot/jit_exec/jit_context_pool.h index f7f7a0a51..cb6152a85 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_context_pool.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_context_pool.h @@ -30,6 +30,31 @@ #include "jit_context.h" namespace JitExec { +/** @struct A sub-pool of JIT context objects. */ +struct __attribute__((packed)) JitContextSubPool { + /** @var The array of pooled JIT context objects. */ + MotJitContext* m_contextPool; // L1 offset 0 + + /** @var The list of free JIT context objects. */ + MotJitContext* m_freeContextList; // L1 offset 8 + + /** @var The number of free JIT context objects. */ + uint32_t m_freeContextCount; // L1 offset 16 + + /** @var The unique pool identifier. */ + uint16_t m_subPoolId; // L1 offset 20 + + /** @var Align struct size to cache line. */ + uint8_t m_padding3[42]; // L1 offset 22 + +#ifdef MOT_JIT_DEBUG + int* m_freeContextIds; +#endif +}; + +/** @define The size of each sub-pool is fixed at 256 items, so each sub-pool takes about 66 KB. */ +#define JIT_SUB_POOL_SIZE 256 + /** @struct A pool of JIT context objects. */ struct __attribute__((packed)) JitContextPool { /** @var A lock to synchronize pool access. */ @@ -39,25 +64,25 @@ struct __attribute__((packed)) JitContextPool { uint8_t m_padding1[60]; /** @var The array of pooled JIT context objects. */ - JitContext* m_contextPool; // L1 offset 0 + JitContextSubPool** m_subPools; // L1 offset 0 - /** @var The size of the pool. */ + /** @var The maximum size of the pool (number of objects). */ uint32_t m_poolSize; // L1 offset 8 + /** @var The maximum number of sub-pools. */ + uint16_t m_subPoolCount; // L1 offset 12 + /** @var The usage of this context pool (global or session-local). */ - JitContextUsage m_usage; // L1 offset 12 (1 byte) + JitContextUsage m_usage; // L1 offset 14 (1 byte) /** @var Align next pointer to 8 bytes. */ - uint8_t m_padding2[3]; // L1 offset 13 + uint8_t m_padding2[1]; // L1 offset 15 - /** @var The list of free JIT context objects. */ - JitContext* m_freeContextList; // L1 offset 16 + /** @var The unique pool identifier. */ + uint32_t m_poolId; // L1 offset 16 - /** @var The number of free JIT context objects. */ - uint32_t m_freeContextCount; // L1 offset 24 - - /** @var Align struct size to 2 cache lines. */ - uint8_t m_padding3[36]; // align to cache line + /** @var Align struct size to cache line. */ + uint8_t m_padding3[44]; // L1 offset 20 }; /** @@ -81,14 +106,17 @@ extern void DestroyJitContextPool(JitContextPool* contextPool); * @return The JIT context if allocation succeeded, otherwise NULL. Consult @ref * mm_get_root_error() for futher details. */ -JitContext* AllocPooledJitContext(JitContextPool* contextPool); +MotJitContext* AllocPooledJitContext(JitContextPool* contextPool); /** * @brief Returns a JIT context to a pool. * @param contextPool The JIT context pool into which the context object is to be returned. * @param jitContext The JIT context to return. */ -extern void FreePooledJitContext(JitContextPool* contextPool, JitContext* jitContext); +extern void FreePooledJitContext(JitContextPool* contextPool, MotJitContext* jitContext); + +/** @brief Retrieves the actual size in bytes that an allocated JIT context takes. */ +extern uint32_t GetJitContextSize(); } // namespace JitExec #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_exec.cpp b/src/gausskernel/storage/mot/jit_exec/jit_exec.cpp index bf6ee4558..8b5251e83 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_exec.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_exec.cpp @@ -41,16 +41,19 @@ #include "utils/numeric.h" #include "utils/numeric_gs.h" #include "catalog/pg_aggregate.h" +#include "executor/spi.h" +#include "executor/executor.h" #include "mot_internal.h" #include "storage/mot/jit_exec.h" #include "jit_common.h" #include "jit_llvm_query_codegen.h" -#include "jit_tvm_query_codegen.h" +#include "jit_llvm_sp_codegen.h" #include "jit_source_pool.h" #include "jit_source_map.h" #include "jit_context_pool.h" #include "jit_plan.h" +#include "jit_plan_sp.h" #include "jit_statistics.h" #include "mot_engine.h" @@ -59,88 +62,411 @@ #include "mot_error.h" #include "utilities.h" #include "cycles.h" +#include "mot_atomic_ops.h" +#include "jit_profiler.h" -#include +#include namespace JitExec { DECLARE_LOGGER(LiteExecutor, JitExec); #ifdef MOT_JIT_TEST -static uint64_t totalExecCount = 0; +uint64_t totalQueryExecCount = 0; +uint64_t totalFunctionExecCount = 0; +static void UpdateTestStats(MotJitContext* jitContext, int newScan); #endif -extern JitPlan* IsJittable(Query* query, const char* queryString) -{ - JitPlan* jitPlan = NULL; - bool limitBreached = false; +static void JITXactCallback(XactEvent event, void* arg); +static MotJitContext* MakeDummyContext(JitSource* jitSource, JitContextUsage usage); +inline void PrepareSessionAccess() +{ // when running under thread-pool, it is possible to be executed from a thread that hasn't yet executed any MOT code // and thus lacking a thread id and NUMA node id. Nevertheless, we can be sure that a proper session context is set // up. - EnsureSafeThreadAccess(); + (void)EnsureSafeThreadAccess(); // since we use session local allocations and a session context might have not been created yet, we make sure such // one exists now - GetSafeTxn(__FUNCTION__); + (void)GetSafeTxn(__FUNCTION__); + + // make sure we get commit/rollback notifications in this session + if (!u_sess->mot_cxt.jit_xact_callback_registered) { + MOT_LOG_TRACE("Registering transaction call back for current session"); + RegisterXactCallback(JITXactCallback, nullptr); + u_sess->mot_cxt.jit_xact_callback_registered = true; + } +} + +static bool PrepareIsJittable(const char* queryName, bool isFunction) +{ + // since this might be called through initdb, we make a safety check here + if (MOT::MOTEngine::GetInstance() == nullptr) { + return false; + } + + // ensure all MOT thread/session-local identifiers are in place + PrepareSessionAccess(); // check limit not breached if (u_sess->mot_cxt.jit_context_count >= GetMotCodegenLimit()) { - MOT_LOG_TRACE("Query is not jittable: Reached the maximum of %d JIT contexts per session", + MOT_LOG_TRACE("%s %s is not jittable: Reached the maximum of %u JIT contexts per session", + isFunction ? "Stored procedure" : "query", + queryName, u_sess->mot_cxt.jit_context_count); - limitBreached = true; - } else if ((query->commandType == CMD_UPDATE) || (query->commandType == CMD_INSERT) || - (query->commandType == CMD_SELECT) || - (query->commandType == CMD_DELETE)) { // silently ignore other commands - // first check if already exists in global source - if (ContainsReadyCachedJitSource(queryString)) { - MOT_LOG_TRACE("Query JIT source already exists, reporting query is jittable"); - jitPlan = MOT_READY_JIT_PLAN; + JitStatisticsProvider::GetInstance().AddUnjittableLimitQuery(); + return false; + } + return true; +} + +extern JitPlan* IsJittableQuery(Query* query, const char* queryString, bool forcePlan /* = false */) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitPlan* jitPlan = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + + // push currently parsed query + void* prevQuery = u_sess->mot_cxt.jit_pg_query; + u_sess->mot_cxt.jit_pg_query = query; + + PG_TRY(); + { + if (!PrepareIsJittable(queryString, false)) { + MOT_LOG_TRACE("IsJittableQuery(): Failed to prepare for query parsing"); } else { - // either query never parsed or is expired, so we generate a plan - // print query - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - char* parsedQuery = nodeToString(query); - MOT_LOG_TRACE("Checking if query string is jittable: %s\nParsed query:\n%s", queryString, parsedQuery); - pfree(parsedQuery); + // silently ignore other commands + bool isSupported = ((query->commandType == CMD_UPDATE) || (query->commandType == CMD_INSERT) || + (query->commandType == CMD_SELECT) || (query->commandType == CMD_DELETE)); + if (isSupported) { + // first check if already exists in global source + if (!forcePlan && ContainsReadyCachedJitSource(queryString, false, true)) { + MOT_LOG_TRACE("Query JIT source already exists, reporting query is jittable"); + jitPlan = MOT_READY_JIT_PLAN; + } else { + // either query never parsed or is expired, so we generate a plan + // print query + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + char* parsedQuery = nodeToString(query); + MOT_LOG_TRACE( + "Checking if query string is jittable: %s\nParsed query:\n%s\n", queryString, parsedQuery); + pfree(parsedQuery); + } + + // prepare a new plan + jitPlan = JitPreparePlan(query, queryString); + if (jitPlan != nullptr && jitPlan != MOT_READY_JIT_PLAN) { + if (JitPlanHasDistinct((JitPlan*)jitPlan)) { + MOT_LOG_TRACE("Enabling plan with DISTINCT aggregate"); + // In future, if we decide to disallow distinct operator, we just have to call + // JitDestroyPlan here. + } + + // plan generated so query is jittable + MOT_LOG_TRACE("Query is jittable by plan"); + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + JitExplainPlan(query, (JitPlan*)jitPlan); + } + } + } } - // prepare a new plan - jitPlan = JitPreparePlan(query, queryString); - if (jitPlan != nullptr) { - // plan generated so query is jittable, but... - // we disqualify plan if we see distinct operator, until integrated with PG - if (JitPlanHasDistinct(jitPlan)) { - MOT_LOG_TRACE("Disqualifying plan with DISTINCT aggregate"); - JitDestroyPlan(jitPlan); - jitPlan = nullptr; + // update statistics + if (jitPlan == nullptr) { + MOT_LOG_TRACE("Query is not jittable: %s", queryString); + JitStatisticsProvider::GetInstance().AddUnjittableDisqualifiedQuery(); + } else { + // whether generating or cloning code, this is a jittable query + MOT_LOG_TRACE("Query is jittable: %s", queryString); + JitStatisticsProvider::GetInstance().AddJittableQuery(); + } + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while preparing JIT plan for query '%s': %s", queryString, edata->message); + ereport(WARNING, + (errmsg("Failed to parse query '%s' for MOT jitted execution.", queryString), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + if (jitPlan != nullptr) { + JitDestroyPlan((JitPlan*)jitPlan); + jitPlan = nullptr; + } + } + PG_END_TRY(); + + // pop currently parsed query + u_sess->mot_cxt.jit_pg_query = prevQuery; + + if (jitPlan == nullptr) { + MOT_LOG_TRACE("Query is not jittable: %s", queryString); + } + return (JitPlan*)jitPlan; +} + +extern JitPlan* IsJittableFunction( + PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, bool forcePlan /* = false */) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile JitPlan* jitPlan = nullptr; + volatile bool nsPushed = false; + MOT::mot_string qualifiedFunctionName; + volatile const char* qualifiedFunctionNameStr = ""; + char* functionName = ""; + + ++u_sess->mot_cxt.jit_compile_depth; // raise flag to guard SPI calls + u_sess->mot_cxt.jit_parse_error = 0; + PG_TRY(); + { + bool procNameIsNull = false; + bool procSrcIsNull = false; + bool procIsStrictIsNull = false; + Datum procNameDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proname, &procNameIsNull); + Datum procSrcDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_prosrc, &procSrcIsNull); + Datum procIsStrictDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proisstrict, &procIsStrictIsNull); + bool hasNullAttr = (procNameIsNull || procSrcIsNull || procIsStrictIsNull); + functionName = NameStr(*DatumGetName(procNameDatum)); + if (!PrepareIsJittable(functionName, true)) { + MOT_LOG_TRACE("IsJittableQuery(): Failed to prepare for query parsing"); + } else if (hasNullAttr) { + MOT_LOG_TRACE( + "Stored procedure is not jittable: catalog entry for stored procedure contains null attributes"); + } else { + // first check if already exists in global source (all functions defined in global name space) + if (!qualifiedFunctionName.format("%s.%u", functionName, (unsigned)functionOid)) { + MOT_LOG_TRACE("Failed to format qualified function name"); + } else { + qualifiedFunctionNameStr = qualifiedFunctionName.c_str(); + if (!forcePlan && ContainsReadyCachedJitSource((const char*)qualifiedFunctionNameStr, true, true)) { + MOT_LOG_TRACE("Stored procedure JIT source already exists, reporting stored procedure is jittable"); + jitPlan = MOT_READY_JIT_PLAN; } else { - MOT_LOG_TRACE("Query %s jittable by plan", jitPlan ? "is" : "is not"); - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - JitExplainPlan(query, jitPlan); + // either function never parsed or is expired, so we generate a plan + if (PushJitSourceNamespace(functionOid, (const char*)qualifiedFunctionNameStr)) { + nsPushed = true; + + // get required function attributes + char* functionSource = TextDatumGetCString(procSrcDatum); + bool isStrict = DatumGetBool(procIsStrictDatum); + + // print query + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + MOT_LOG_TRACE("Checking if function '%s' is jittable:\n%s\n", + qualifiedFunctionNameStr, + functionSource); + plpgsql_dumptree(function); + } + + // prepare a new plan + jitPlan = JitPrepareFunctionPlan(function, functionSource, functionName, functionOid, isStrict); + if (jitPlan != nullptr) { + MOT_LOG_TRACE("Function %s is jittable by plan", qualifiedFunctionNameStr); + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + JitExplainPlan(function, (JitPlan*)jitPlan); + } + } + PopJitSourceNamespace(); + nsPushed = false; } } + + // update statistics + if (jitPlan == nullptr) { + MOT_LOG_TRACE("Function is not jittable: %s", qualifiedFunctionNameStr); + JitStatisticsProvider::GetInstance().AddUnjittableDisqualifiedQuery(); + } else { + // whether generating or cloning code, this is a jittable query + MOT_LOG_TRACE("Function is jittable: %s", qualifiedFunctionNameStr); + JitStatisticsProvider::GetInstance().AddJittableQuery(); + } } } } - - MOT_LOG_TRACE("Query %s jittable: %s", (jitPlan != nullptr) ? "is" : "is not", queryString); - - // update statistics - if (limitBreached) { - MOT_ASSERT(jitPlan == nullptr); - JitStatisticsProvider::GetInstance().AddUnjittableLimitQuery(); - } else if (jitPlan == nullptr) { - JitStatisticsProvider::GetInstance().AddUnjittableDisqualifiedQuery(); - } else { - // whether generating or cloning code, this is a jittable query - JitStatisticsProvider::GetInstance().AddJittableQuery(); + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while preparing JIT plan for stored procedure '%s': %s", + qualifiedFunctionNameStr, + edata->message); + ereport(WARNING, + (errmsg("Failed to parse function '%s' for MOT jitted execution.", qualifiedFunctionNameStr), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + if (nsPushed) { + PopJitSourceNamespace(); + } + if (jitPlan != nullptr) { + JitDestroyPlan((JitPlan*)jitPlan); + jitPlan = nullptr; + } } + PG_END_TRY(); - return jitPlan; + // decrement SPI guard counter + --u_sess->mot_cxt.jit_compile_depth; + + if (jitPlan == nullptr) { + if (u_sess->mot_cxt.jit_parse_error != 0) { + MOT_LOG_WARN("Stored procedure is not jittable: %s", qualifiedFunctionNameStr); + } else { + MOT_LOG_TRACE("Stored procedure is not jittable: %s", qualifiedFunctionNameStr); + } + } + return (JitPlan*)jitPlan; } -static void ProcessJitResult(MOT::RC result, JitContext* jitContext, int newScan) +extern bool IsInvokeReadyFunction(Query* query) { + // if function code generation is altogether disabled, then we silently fail + if (!IsMotSPCodegenEnabled()) { + return false; + } + if (query->commandType != CMD_SELECT) { + MOT_LOG_DEBUG("IsInvokeReadyFunction(): Disqualifying invoke query - not a SELECT command"); + return false; + } + if (!CheckQueryAttributes(query, false, false, false)) { + MOT_LOG_DEBUG("IsInvokeReadyFunction(): Disqualifying invoke query - Invalid query attributes"); + return false; + } + if ((query->jointree) && (query->jointree->fromlist || query->jointree->quals)) { + MOT_LOG_DEBUG("IsInvokeReadyFunction(): Disqualifying invoke query - FROM clause is not empty"); + return false; + } + if (list_length(query->targetList) != 1) { + MOT_LOG_DEBUG( + "IsInvokeReadyFunction(): Disqualifying invoke query - target list does not contain exactly one entry"); + return false; + } + + TargetEntry* targetEntry = (TargetEntry*)linitial(query->targetList); + if (targetEntry->expr->type != T_FuncExpr) { + MOT_LOG_DEBUG( + "IsInvokeReadyFunction(): Disqualifying invoke query - single target entry is not a function expression"); + return false; + } + + // get function name first + FuncExpr* funcExpr = (FuncExpr*)targetEntry->expr; + char* funcName = get_func_name(funcExpr->funcid); + if (funcName == nullptr) { + MOT_LOG_TRACE("IsInvokeReadyFunction(): Disqualifying invoke query - Failed to find function name for function " + "id %u", + (unsigned)funcExpr->funcid); + return false; + } + MOT::mot_string qualifiedFunctionName; + if (!qualifiedFunctionName.format("%s.%u", funcName, (unsigned)funcExpr->funcid)) { + MOT_LOG_TRACE("IsInvokeReadyFunction(): Disqualifying invoke query - Failed to format qualified function name " + "for function %s", + funcName); + pfree(funcName); + return false; + } + bool result = ContainsReadyCachedJitSource(qualifiedFunctionName.c_str(), true); + if (!result) { + MOT_LOG_TRACE("IsInvokeReadyFunction(): Disqualifying invoke query - could not find a ready context for " + "function %s", + funcName); + } + pfree(funcName); + return result; +} + +static void ProcessErrorResult(MOT::RC result, MotJitContext* jitContext, int newScan) +{ + if (result == MOT::RC_JIT_SP_EXCEPTION) { + if (IsJitSubContextInline(jitContext)) { +#ifdef MOT_JIT_TEST + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY && newScan) { + UpdateTestStats(jitContext, newScan); + } +#endif + return; + } + + const char* errorDetail = nullptr; + const char* errorHint = nullptr; + bytea* txt = DatumGetByteaP(jitContext->m_execState->m_errorMessage); + const char* errorMessage = (const char*)VARDATA(txt); + if (jitContext->m_execState->m_errorDetail != 0) { + bytea* txtd = DatumGetByteaP(jitContext->m_execState->m_errorDetail); + errorDetail = (const char*)VARDATA(txtd); + } + if (jitContext->m_execState->m_errorHint != 0) { + bytea* txth = DatumGetByteaP(jitContext->m_execState->m_errorHint); + errorHint = (const char*)VARDATA(txth); + } + + RaiseEreport(jitContext->m_execState->m_sqlState, errorMessage, errorDetail, errorHint); + } + + if (result != MOT::RC_OK) { + void* arg1 = nullptr; + void* arg2 = nullptr; + + MOT::TxnManager* currTxn = u_sess->mot_cxt.jit_txn; + + // prepare message argument for error report + ColumnDef stub; + if (result == MOT::RC_UNIQUE_VIOLATION) { + arg1 = (void*)(currTxn->m_errIx ? currTxn->m_errIx->GetName().c_str() : ""); + arg2 = (void*)(currTxn->m_errMsgBuf); + } else if (result == MOT::RC_NULL_VIOLATION) { + MOT::Table* table = jitContext->m_execState->m_nullViolationTable; + stub.colname = table->GetField((uint64_t)jitContext->m_execState->m_nullColumnId)->m_name; + arg1 = (void*)&stub; + arg2 = (void*)table; + } + + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + MOT_LOG_ERROR_STACK("Failed to execute jitted function with error code: %d (%s), query is: %s", + (int)result, + MOT::RcToString(result), + jitContext->m_queryString); + } + if (result == MOT::RC_ABORT) { + JitStatisticsProvider::GetInstance().AddAbortExecQuery(); + } else { + JitStatisticsProvider::GetInstance().AddFailExecQuery(); + } + report_pg_error(result, (void*)arg1, (void*)arg2); + } +} + +static void ProcessJitResult(MOT::RC result, MotJitContext* jitContext, int newScan) +{ + // the result is being returned from jitted query + if (result == MOT::RC_LOCAL_ROW_NOT_FOUND && jitContext->m_rc != MOT::RC_OK) { + switch (jitContext->m_rc) { + case MOT::RC_SERIALIZATION_FAILURE: + case MOT::RC_MEMORY_ALLOCATION_ERROR: + case MOT::RC_ABORT: + result = jitContext->m_rc; + jitContext->m_rc = MOT::RC_OK; + break; + + default: + MOT_LOG_ERROR("ProcessJitResult(): Unsupported error (%s), changing result to ABORT", + RcToString(jitContext->m_rc)); + MOT_ASSERT(false); + result = MOT::RC_ABORT; + jitContext->m_rc = MOT::RC_OK; + } + } + + // we want to make sure that jitContext->m_rc was already handled. jitContext->m_rc != RC_OK is an unexpected + // behavior. + MOT_ASSERT(jitContext->m_rc == MOT::RC_OK); // NOTE: errors might be reported in a better way, so this part can be reviewed sometime // we ignore "local row not found" in SELECT and DELETE scenarios if (result == MOT::RC_LOCAL_ROW_NOT_FOUND) { @@ -154,12 +480,12 @@ static void ProcessJitResult(MOT::RC result, JitContext* jitContext, int newScan case JIT_COMMAND_COMPOUND_SELECT: case JIT_COMMAND_UPDATE: case JIT_COMMAND_RANGE_UPDATE: + case JIT_COMMAND_RANGE_DELETE: // this is considered as successful execution JitStatisticsProvider::GetInstance().AddInvokeQuery(); if (newScan) { #ifdef MOT_JIT_TEST - MOT_ATOMIC_INC(totalExecCount); - MOT_LOG_INFO("JIT total queries executed: %" PRIu64, MOT_ATOMIC_LOAD(totalExecCount)); + UpdateTestStats(jitContext, newScan); #endif JitStatisticsProvider::GetInstance().AddExecQuery(); } @@ -169,118 +495,216 @@ static void ProcessJitResult(MOT::RC result, JitContext* jitContext, int newScan } } - const char* arg1 = ""; - const char* arg2 = ""; - - MOT::TxnManager* currTxn = u_sess->mot_cxt.jit_txn; - - // prepare message argument for error report - if (result == MOT::RC_UNIQUE_VIOLATION) { - arg1 = currTxn->m_errIx ? currTxn->m_errIx->GetName().c_str() : ""; - arg2 = currTxn->m_errMsgBuf; - } else if (result == MOT::RC_NULL_VIOLATION) { - arg1 = jitContext->m_table->GetField((uint64_t)jitContext->m_nullColumnId)->m_name; - arg2 = jitContext->m_table->GetLongTableName().c_str(); - } - - if (result != MOT::RC_OK) { - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - MOT_LOG_ERROR_STACK( - "Failed to execute jitted function with error code: %d (%s)", (int)result, MOT::RcToString(result)); - } - if (result == MOT::RC_ABORT) { - JitStatisticsProvider::GetInstance().AddAbortExecQuery(); - } else { - JitStatisticsProvider::GetInstance().AddFailExecQuery(); - } - report_pg_error(result, (void*)arg1, (void*)arg2); - } + ProcessErrorResult(result, jitContext, newScan); } -static JitContext* GenerateJitContext(Query* query, const char* queryString, JitPlan* jitPlan, JitSource* jitSource) +static MotJitContext* ProcessGeneratedJitContext(JitSource* jitSource, MotJitContext* sourceJitContext, + const char* queryString, JitCodegenStats* codegenStats, JitContextUsage usage) { - JitContext* jitContext = nullptr; - JitContext* sourceJitContext = nullptr; - - uint64_t startTime = GetSysClock(); - if (g_instance.mot_cxt.jitExecMode == JIT_EXEC_MODE_LLVM) { - MOT_LOG_TRACE("Generating LLVM JIT context for query: %s", queryString); - sourceJitContext = JitCodegenLlvmQuery(query, queryString, jitPlan); - } else { - MOT_ASSERT(g_instance.mot_cxt.jitExecMode == JIT_EXEC_MODE_TVM); - MOT_LOG_TRACE("Generating TVM JIT context for query: %s", queryString); - sourceJitContext = JitCodegenTvmQuery(query, queryString, jitPlan); - } - uint64_t endTime = GetSysClock(); - + JitCodegenState newState = JitCodegenState::JIT_CODEGEN_NONE; if (sourceJitContext == nullptr) { // notify error for all waiters - this query will never again be JITTed - cleanup only during database shutdown - MOT_LOG_TRACE("Failed to generate code for query, signaling error context for query: %s", queryString); - SetJitSourceError(jitSource, MOT::GetRootError()); - JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); - } else { - MOT_LOG_TRACE("Generated JIT context %p for query: %s", sourceJitContext, queryString); - MOT_LOG_TRACE("Cloning ready source context %p", sourceJitContext); - jitContext = CloneJitContext(sourceJitContext); - if (jitContext != nullptr) { - if (SetJitSourceReady(jitSource, sourceJitContext)) { - MOT_LOG_TRACE("Installed ready JIT context %p for query: %s", sourceJitContext, queryString); - ++u_sess->mot_cxt.jit_context_count; - AddJitSourceContext(jitSource, jitContext); // register for cleanup due to DDL - jitContext->m_jitSource = jitSource; - jitContext->m_queryString = jitSource->_query_string; - // update statistics - MOT_LOG_TRACE("Registered JIT context %p in JIT source %p for cleanup", jitContext, jitSource); - JitStatisticsProvider& instance = JitStatisticsProvider::GetInstance(); - instance.AddCodeGenTime(MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime)); - instance.AddCodeGenQuery(); - instance.AddCodeCloneQuery(); - } else { - // this is illegal state transition error in JIT source (internal bug) - // there is already a JIT context present in the JIT source (maybe generated by another session, - // although impossible), so we just fail JIT for this session (other sessions may still benefit from - // existing JIT context). Pay attention that we cannot replace the JIT context in the JIT source, since - // the JIT function in it is probably still being used by other sessions - MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, - "JIT Compile", - "Failed to set ready source context %p, disqualifying query: %s", - sourceJitContext, - queryString); - DestroyJitContext(sourceJitContext); - DestroyJitContext(jitContext); - jitContext = nullptr; - JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); - } - } else { - // this is not expected to happen (because the number of context objects per session equals number of JIT - // source objects), but we still must set JIT source state to error, otherwise all waiting sessions will - // never wakeup - SetJitSourceError(jitSource, MOT::GetRootError()); - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_UNAVAILABLE, - "JIT Compile", - "Failed to clone ready source context %p, disqualifying query: %s", - sourceJitContext, - queryString); - DestroyJitContext(sourceJitContext); - JitStatisticsProvider::GetInstance().AddCodeCloneErrorQuery(); + MOT_LOG_TRACE("Failed to generate code for query with JIT source %p, signaling error context for query: %s", + jitSource, + queryString); + SetJitSourceError(jitSource, MOT::GetRootError(), &newState); + if (newState == JitCodegenState::JIT_CODEGEN_DEPRECATE) { + // ATTENTION: Do not use the jitSource after this point, as it might be removed from the deprecated list + // and freed by other sessions. + CleanupConcurrentlyDroppedSPSource(queryString); } + JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); + return nullptr; } + MOT_LOG_TRACE("Generated JIT context %p for query: '%s', codegenTime: %" PRIu64 " micros", + sourceJitContext, + queryString, + codegenStats->m_codegenTime); + + bool isSPGlobalSource = false; + if ((sourceJitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (jitSource->m_usage == JIT_CONTEXT_GLOBAL)) { + isSPGlobalSource = true; + } + + if (!SetJitSourceReady(jitSource, sourceJitContext, codegenStats, &newState)) { + // Two possible causes: + // 1. Stored procedure was dropped and deprecated when we were compiling. In this case, we just fail + // JIT code generation to trigger re-validation. + // 2. Illegal state transition error in JIT source (internal bug), there is already a JIT context present + // in the JIT source (maybe generated by another session, although impossible). + // So we just fail JIT for this session (other sessions may still benefit from existing JIT context). + // Pay attention that we cannot replace the JIT context in the JIT source, since + // the JIT function in it is probably still being used by other sessions + MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, + "JIT Compile", + "Failed to set ready source context %p to JIT source %p (newState %s), disqualifying query: %s", + sourceJitContext, + jitSource, + JitCodegenStateToString(newState), + queryString); + if (newState == JitCodegenState::JIT_CODEGEN_DEPRECATE) { + // ATTENTION: Do not use the jitSource after this point, as it might be removed from the deprecated list + // and freed by other sessions. + CleanupConcurrentlyDroppedSPSource(queryString); + } + DestroyJitContext(sourceJitContext); + JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); + return nullptr; + } + + MOT_LOG_TRACE("Installed ready JIT source context %p to JIT source %p (newState %s) for query: %s", + sourceJitContext, + jitSource, + JitCodegenStateToString(newState), + queryString); + + bool needPruning = false; + MotJitContext* jitContext = nullptr; + if (isSPGlobalSource) { + // If this is a global SP source, lock the source map for pruning. + LockJitSourceMap(); + } + + LockJitSource(jitSource); + // attention: the source context has its JIT source set up properly, so clone can use it + JitCodegenState codegenState = jitSource->m_codegenState; + if (jitSource->m_sourceJitContext == sourceJitContext) { + if (codegenState == JitCodegenState::JIT_CODEGEN_READY) { + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (jitSource->m_usage == JIT_CONTEXT_GLOBAL)) { + needPruning = true; + MOT_ASSERT(isSPGlobalSource); + } + jitContext = CloneJitContext(sourceJitContext, usage); + } else if (codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE || + codegenState == JitCodegenState::JIT_CODEGEN_PENDING) { + jitContext = MakeDummyContext(jitSource, usage); + } else { + MOT_LOG_TRACE("Cannot clone context from JIT source %p after code-generation, unexpected state %s: %s", + jitSource, + JitCodegenStateToString(codegenState), + jitSource->m_queryString); + } + } else { + MOT_LOG_TRACE("JIT source %p (codegenState %s) context %p changed concurrently to %p after code-generation", + jitSource, + JitCodegenStateToString(codegenState), + sourceJitContext, + jitSource->m_sourceJitContext); + } + UnlockJitSource(jitSource); + + if (isSPGlobalSource) { + if (needPruning) { + MOT_LOG_TRACE("Pruning global ready function source %p after code-generation: %s", + jitSource, + jitSource->m_queryString); + PruneNamespace(jitSource); + } + UnlockJitSourceMap(); + } + + if (jitContext == nullptr) { + // we keep the source JIT context intact in the JIT source, maybe some time later we will have resources for + // cloning. + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_UNAVAILABLE, + "JIT Compile", + "Failed to clone ready source context %p from JIT source %p (codegenState %s), disqualifying query: %s", + sourceJitContext, + jitSource, + JitCodegenStateToString(codegenState), + queryString); + JitStatisticsProvider::GetInstance().AddCodeCloneErrorQuery(); + return nullptr; + } + + MOT_LOG_TRACE("Registered JIT context %p in JIT source %p for cleanup", jitContext, jitSource); + + // update statistics + JitStatisticsProvider& instance = JitStatisticsProvider::GetInstance(); + instance.AddCodeGenTime(codegenStats->m_codegenTime); + instance.AddCodeGenQuery(); + instance.AddCodeCloneQuery(); + return jitContext; } -extern JitContext* JitCodegenQuery(Query* query, const char* queryString, JitPlan* jitPlan) +static MotJitContext* GenerateQueryJitContext( + Query* query, const char* queryString, JitPlan* jitPlan, JitSource* jitSource, JitContextUsage usage) { - JitContext* jitContext = nullptr; + MotJitContext* sourceJitContext = nullptr; + JitCodegenStats codegenStats = {}; - MOT_LOG_TRACE("*** Generating cached code for query: %s", queryString); + uint64_t startTime = GetSysClock(); + MOT_LOG_TRACE("Generating LLVM JIT context for query: %s", queryString); + sourceJitContext = JitCodegenLlvmQuery(query, queryString, jitPlan, codegenStats); + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_codegenTime = timeMicros; + + return ProcessGeneratedJitContext(jitSource, sourceJitContext, queryString, &codegenStats, usage); +} + +static MotJitContext* GenerateFunctionJitContext(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, + ReturnSetInfo* returnSetInfo, const char* queryString, JitPlan* jitPlan, JitSource* jitSource, + JitContextUsage usage) +{ + MotJitContext* sourceJitContext = nullptr; + JitCodegenStats codegenStats = {}; + uint64_t startTime = GetSysClock(); + MOT_LOG_TRACE("Generating LLVM JIT context for function: %s", queryString); + sourceJitContext = JitCodegenLlvmFunction(function, procTuple, functionOid, returnSetInfo, jitPlan, codegenStats); + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_codegenTime = timeMicros; + + return ProcessGeneratedJitContext(jitSource, sourceJitContext, queryString, &codegenStats, usage); +} + +static MotJitContext* MakeDummyContext(JitSource* jitSource, JitContextUsage usage) +{ + JitQueryContext* jitContext = (JitQueryContext*)AllocJitContext(usage, jitSource->m_contextType); + if (jitContext != nullptr) { + jitContext->m_contextType = jitSource->m_contextType; + jitContext->m_commandType = jitSource->m_commandType; + jitContext->m_validState = JIT_CONTEXT_PENDING_COMPILE; + AddJitSourceContext(jitSource, jitContext); + } + return jitContext; +} + +static void SetupJitSourceForCodegen(JitSource* jitSource, JitContextType contextType, JitPlan* jitPlan) +{ + LockJitSource(jitSource); + MOT_ASSERT(jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE); + MOT_ASSERT(jitPlan != MOT_READY_JIT_PLAN); // we must have a ready plan + jitSource->m_contextType = contextType; + jitSource->m_commandType = jitPlan->_command_type; + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType == JIT_COMMAND_INVOKE)) { + jitSource->m_functionOid = ((JitInvokePlan*)jitPlan)->_function_id; + } else if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + jitSource->m_functionOid = ((JitFunctionPlan*)jitPlan)->_function_id; + } + UnlockJitSource(jitSource); +} + +template +static MotJitContext* JitCodegen( + const char* queryName, JitPlan* jitPlan, Generator& generator, bool globalNamespace, JitContextUsage usage) +{ + MotJitContext* jitContext = nullptr; + + MOT_LOG_TRACE("*** Generating cached code for query: %s", queryName); MOT_ASSERT(jitPlan); // search for a cached JIT source - either use an existing one or generate a new one JitSource* jitSource = nullptr; do { // instead of goto LockJitSourceMap(); // jit-source already exists, so wait for it to become ready - jitSource = GetCachedJitSource(queryString); + jitSource = GetCachedJitSource(queryName, globalNamespace); if (jitSource != nullptr) { MOT_LOG_TRACE("Found a jit-source %p", jitSource); UnlockJitSourceMap(); @@ -288,204 +712,1407 @@ extern JitContext* JitCodegenQuery(Query* query, const char* queryString, JitPla // ATTENTION: JIT source cannot get expired at this phase, since DDL statements (that might cause the // JIT Source to expire) cannot run in parallel with other statements // Note: cached context was found, but maybe it is still being compiled, so we wait for it to be ready - MOT_LOG_TRACE("Waiting for context to be ready: %s", queryString); - JitContextStatus ctxStatus = WaitJitContextReady(jitSource, &jitContext); - if (ctxStatus == JIT_CONTEXT_READY) { - MOT_LOG_TRACE("Context is ready: %s", queryString); - } else if (ctxStatus == JIT_CONTEXT_EXPIRED) { // need to regenerate context - // generate JIT context, install it and notify - MOT_LOG_TRACE("Regenerating real JIT code for expired context: %s", queryString); + MOT_LOG_TRACE("Waiting for context to be ready: %s", queryName); + JitCodegenState codegenState = GetReadyJitContext(jitSource, &jitContext, usage, jitPlan); + if (codegenState == JitCodegenState::JIT_CODEGEN_READY) { + MOT_LOG_TRACE("Collected ready context: %s", queryName); + } else if (codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) { + // regenerate JIT context, install it and notify + MOT_LOG_TRACE("Regenerating real JIT code for expired context: %s", queryName); // we must prepare the analysis variables again (because we did not call IsJittable()) // in addition, table/index definition might have change so we must re-analyze - if (jitPlan != MOT_READY_JIT_PLAN) { - JitDestroyPlan(jitPlan); + if (jitPlan == MOT_READY_JIT_PLAN) { + jitPlan = generator.IsJittable(); } - jitPlan = IsJittable(query, queryString); if (jitPlan == nullptr) { - MOT_LOG_TRACE("Failed to re-analyze expired JIT source, notifying error status: %s", queryString); + MOT_LOG_TRACE("Failed to re-analyze expired JIT source, notifying error status: %s", queryName); SetJitSourceError(jitSource, MOT_ERROR_INTERNAL); } else { - jitContext = GenerateJitContext(query, queryString, jitPlan, jitSource); + JitFunctionPlan* functionPlan = nullptr; + if (JitPlanHasFunctionPlan(jitPlan, &functionPlan)) { + // fn_xmin equal to m_functionTxnId is possible, if the JIT source moved from ERROR to + // UNAVAILABLE/EXPIRED in GetReadyJitContext. + MOT_ASSERT((functionPlan->m_function->fn_xmin >= jitSource->m_functionTxnId) && + (functionPlan->m_function->fn_xmin >= jitSource->m_expireTxnId)); + } + SetupJitSourceForCodegen(jitSource, generator.GetContextType(), jitPlan); + jitContext = generator.GenerateJitContext(jitSource, jitPlan, usage); } - } else { // code generation (by another session) failed - MOT_LOG_TRACE("Cached context status is not ready: %s", queryString); + } else if ((codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) || + (codegenState == JitCodegenState::JIT_CODEGEN_PENDING)) { + // prepare a dummy context + jitContext = MakeDummyContext(jitSource, usage); + MOT_LOG_TRACE("Generated dummy context %p for query: %s", jitContext, queryName); + } else { // code generation (by another session) failed, or source was deprecated + MOT_LOG_TRACE("Cached context status is not ready: %s", queryName); break; // goto cleanup } - } else { // jit-source is not ready, so we need to generate code - MOT_ASSERT(jitPlan != MOT_READY_JIT_PLAN); // we must have a real plan, right? - MOT_LOG_TRACE("JIT-source not found, generating code for query: %s", queryString); - if (GetJitSourceMapSize() == GetMotCodegenLimit()) { - MOT_LOG_DEBUG("Skipping query code generation: Reached total maximum of JIT source objects %d", + } else { + // JIT source is not ready, so we need to generate code. + MOT_LOG_TRACE("JIT-source not found, generating code for query: %s", queryName); + + if (generator.GetContextType() == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitFunctionPlan* functionPlan = (JitFunctionPlan*)jitPlan; + Oid functionOid = functionPlan->_function_id; + if (IsDroppedSP(functionOid)) { + MOT_LOG_ERROR("Function is already deprecated, skipping premature re-validation: %s", queryName); + UnlockJitSourceMap(); + break; // goto cleanup + } + } + + // we allocate an empty cached entry and install it in the global map, other threads can wait until it + // is ready (thus only 1 thread regenerates code) + JitContextUsage sourceUsage = + (u_sess->mot_cxt.jit_session_source_map != nullptr) ? JIT_CONTEXT_LOCAL : JIT_CONTEXT_GLOBAL; + jitSource = AllocJitSource(queryName, sourceUsage); + if (jitSource == nullptr) { + MOT_LOG_TRACE("Skipping query code generation: Reached total maximum of JIT source objects %d", GetMotCodegenLimit()); UnlockJitSourceMap(); break; // goto cleanup } - // we allocate an empty cached entry and install it in the global map, other threads can wait until it - // is ready (thus only 1 thread regenerates code) - jitSource = AllocPooledJitSource(queryString); - if (jitSource != nullptr) { - MOT_LOG_TRACE("Created jit-source object %p", jitSource); - if (!AddCachedJitSource(jitSource)) { // unexpected: entry already exists (this is a bug) - MOT_LOG_TRACE("Failed to add jit-source object to map"); - UnlockJitSourceMap(); - FreePooledJitSource(jitSource); - break; // goto cleanup - } - UnlockJitSourceMap(); // let other operations continue - // generate JIT context, install it and notify - MOT_LOG_TRACE("Generating JIT code"); - jitContext = GenerateJitContext(query, queryString, jitPlan, jitSource); + MOT_ASSERT(jitPlan != MOT_READY_JIT_PLAN); // we must have a ready plan + + SetupJitSourceForCodegen(jitSource, generator.GetContextType(), jitPlan); + + MOT_LOG_TRACE( + "Created jit-source object %p (contextType %s, commandType %s, functionOid %u) during code-gen: %s", + jitSource, + JitContextTypeToString(jitSource->m_contextType), + CommandToString(jitSource->m_commandType), + jitSource->m_functionOid, + queryName); + + if (!AddCachedJitSource(jitSource, globalNamespace)) { + // unexpected: entry already exists (this is a bug) + MOT_LOG_TRACE("Failed to add jit-source object to map"); + UnlockJitSourceMap(); + DestroyJitSource(jitSource); + jitSource = nullptr; + break; // goto cleanup } + + UnlockJitSourceMap(); // let other operations continue + + // generate JIT context, install it and notify + MOT_LOG_TRACE("Generating JIT code"); + jitContext = generator.GenerateJitContext(jitSource, jitPlan, usage); } } while (0); - // cleanup - if ((jitPlan != nullptr) && (jitPlan != MOT_READY_JIT_PLAN)) { - JitDestroyPlan(jitPlan); - } - return jitContext; } -extern void JitResetScan(JitContext* jitContext) +struct JitQueryGenerator { +public: + JitQueryGenerator(Query* query, const char* queryString) : m_query(query), m_queryString(queryString) + {} + + inline JitPlan* IsJittable() + { + return IsJittableQuery(m_query, m_queryString, true); + } + + inline MotJitContext* GenerateJitContext(JitSource* jitSource, JitPlan* jitPlan, JitContextUsage usage) + { + return GenerateQueryJitContext(m_query, m_queryString, jitPlan, jitSource, usage); + } + + inline JitContextType GetContextType() const + { + return JitContextType::JIT_CONTEXT_TYPE_QUERY; + } + +private: + Query* m_query; + const char* m_queryString; +}; + +extern MotJitContext* JitCodegenQuery(Query* query, const char* queryString, JitPlan* jitPlan, JitContextUsage usage) { - MOT_LOG_DEBUG("JitResetScan(): Resetting iteration count for context %p", jitContext); - jitContext->m_iterCount = 0; + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile MotJitContext* result = nullptr; + + // push currently parsed query + void* prevQuery = u_sess->mot_cxt.jit_pg_query; + u_sess->mot_cxt.jit_pg_query = query; + u_sess->mot_cxt.jit_codegen_error = 0; + + PG_TRY(); + { + MOT_LOG_TRACE("Generating code for query: %s", queryString); + uint64_t startTime = GetSysClock(); + JitQueryGenerator generator(query, queryString); + result = JitCodegen(queryString, jitPlan, generator, false, usage); + if (result != nullptr) { + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + MOT_LOG_TRACE("Code generation time %" PRIu64 " micros for query: %s", timeMicros, queryString); + } + } + PG_CATCH(); + { + // cleanup + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while generating code for query '%s': %s", queryString, edata->message); + ereport(WARNING, + (errmsg("Failed to generate code for query '%s' for MOT jitted execution.", queryString), + errdetail("%s", edata->detail))); + u_sess->mot_cxt.jit_codegen_error = edata->sqlerrcode; + FreeErrorData(edata); + FlushErrorState(); + } + PG_END_TRY(); + + // reset compile state for robustness + JitResetCompileState(); + + // pop currently parsed query + u_sess->mot_cxt.jit_pg_query = prevQuery; + + if (result == nullptr) { + MOT_LOG_WARN("Failed to JIT-compile query: %s", queryString); + } + return (MotJitContext*)result; } -extern int JitExecQuery( - JitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded) +extern MotJitContext* TryJitCodegenQuery(Query* query, const char* queryString) { - int result = 0; + MotJitContext* jitContext = nullptr; + // make sure SPI is set-up on top level call + SPIAutoConnect spiAutoConn; + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while generating code for query: %s (%u)", + queryString, + SPI_result_code_string(rc), + rc); + return nullptr; + } + + JitPlan* jitPlan = IsJittableQuery(query, queryString); + if (jitPlan != nullptr) { + jitContext = JitCodegenQuery(query, queryString, jitPlan, JIT_CONTEXT_LOCAL); + if (jitContext == nullptr) { + MOT_LOG_TRACE("Failed to generate jitted MOT code for query: %s", queryString); + } + JitDestroyPlan(jitPlan); + } + return jitContext; +} + +struct JitFunctionGenerator { +public: + JitFunctionGenerator(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, ReturnSetInfo* returnSetInfo, + const char* queryString) + : m_function(function), + m_procTuple(procTuple), + m_functionOid(functionOid), + m_returnSetInfo(returnSetInfo), + m_queryString(queryString) + {} + + inline JitPlan* IsJittable() + { + return IsJittableFunction(m_function, m_procTuple, m_functionOid, true); + } + + inline MotJitContext* GenerateJitContext(JitSource* jitSource, JitPlan* jitPlan, JitContextUsage usage) + { + return GenerateFunctionJitContext( + m_function, m_procTuple, m_functionOid, m_returnSetInfo, m_queryString, jitPlan, jitSource, usage); + } + + inline JitContextType GetContextType() const + { + return JitContextType::JIT_CONTEXT_TYPE_FUNCTION; + } + +private: + PLpgSQL_function* m_function; + HeapTuple m_procTuple; + Oid m_functionOid; + ReturnSetInfo* m_returnSetInfo; + const char* m_queryString; +}; + +extern MotJitContext* JitCodegenFunction(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, + ReturnSetInfo* returnSetInfo, JitPlan* jitPlan, JitContextUsage usage) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile MotJitContext* result = nullptr; + volatile bool nsPushed = false; + MOT::mot_string qualifiedFunctionName; + volatile const char* qualifiedFunctionNameStr = ""; + char* functionName = ""; + + u_sess->mot_cxt.jit_codegen_error = 0; + + PG_TRY(); + { + bool procNameIsNull = false; + Datum procNameDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proname, &procNameIsNull); + if (procNameIsNull) { + MOT_LOG_TRACE( + "Stored procedure is not jittable: catalog entry for stored procedure contains null attributes"); + } else { + // generate function context sub-queries under name-space + functionName = NameStr(*DatumGetName(procNameDatum)); + if (!qualifiedFunctionName.format("%s.%u", functionName, (unsigned)functionOid)) { + MOT_LOG_TRACE("Failed to format qualified function name"); + } else { + qualifiedFunctionNameStr = qualifiedFunctionName.c_str(); + if (PushJitSourceNamespace(functionOid, (const char*)qualifiedFunctionNameStr)) { + nsPushed = true; + MOT_LOG_TRACE("Generating code for function: %s", qualifiedFunctionNameStr); + uint64_t startTime = GetSysClock(); + JitFunctionGenerator generator( + function, procTuple, functionOid, returnSetInfo, (const char*)qualifiedFunctionNameStr); + result = JitCodegen((const char*)qualifiedFunctionNameStr, jitPlan, generator, true, usage); + if (result != nullptr) { + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + MOT_LOG_TRACE("Code generation time %" PRIu64 " micros for function: %s", + timeMicros, + qualifiedFunctionNameStr); + } + } + } + } + } + PG_CATCH(); + { + // cleanup + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while generating code for stored procedure %s: %s", + qualifiedFunctionNameStr, + edata->message); + ereport(WARNING, + (errmsg("Failed to generate code for stored procedure '%s' for MOT jitted execution.", + qualifiedFunctionNameStr), + errdetail("%s", edata->detail))); + u_sess->mot_cxt.jit_codegen_error = edata->sqlerrcode; + FreeErrorData(edata); + FlushErrorState(); + } + PG_END_TRY(); + + // reset compile state for robustness + JitResetCompileState(); + + if (nsPushed) { + PopJitSourceNamespace(); + } + + if (result == nullptr) { + MOT_LOG_WARN("Failed to JIT-compile stored procedure: %s", qualifiedFunctionNameStr); + } + return (MotJitContext*)result; +} + +extern void JitResetScan(MotJitContext* jitContext) +{ + // ATTENTION: due to order of events in exec_bind_message() (JitResetScan is called before TryRevalidateJitContext) + // it is possible that the current JIT context is in temporary compile state, but is about to be undergo successful + // revalidation, so we do not want to miss out the reset-scan operation in this case. + // Therefore, we allow reset-scan for any context with valid execution state. + MOT_LOG_DEBUG("JitResetScan(): Resetting iteration count for context %p", jitContext); + MOT_ASSERT(jitContext->m_contextType != JitContextType::JIT_CONTEXT_TYPE_INVALID); + if (jitContext->m_execState != nullptr) { + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; + if (execState != nullptr) { + execState->m_iterCount = 0; + } + } else if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitFunctionExecState* execState = (JitFunctionExecState*)jitContext->m_execState; + if (execState != nullptr) { + if (execState->m_currentSubQueryId >= 0) { + JitFunctionContext* funcContext = (JitFunctionContext*)jitContext; + MotJitContext* activeQueryContext = + funcContext->m_SPSubQueryList[execState->m_currentSubQueryId].m_queryContext; + if (activeQueryContext != nullptr) { + JitResetScan(activeQueryContext); + } + } + } + } + } +} + +static MOT::RC ValidatePrepare(MotJitContext* jitContext) +{ // make sure we can execute (guard against crash due to logical error) -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Executing JIT context %p with query: %s", jitContext, jitContext->m_queryString); -#endif - if (!jitContext->m_llvmFunction && !jitContext->m_tvmFunction) { + bool missingFunction = false; + if (!jitContext->m_llvmFunction && !jitContext->m_llvmSPFunction) { + missingFunction = true; + } + if (missingFunction) { MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Execute JIT", "Cannot execute jitted function: function is null. Aborting transaction."); JitStatisticsProvider::GetInstance().AddFailExecQuery(); - report_pg_error(MOT::RC_ERROR); // execution control ends, calls ereport(error,...) + if (IsJitSubContextInline(jitContext)) { + return MOT::RC_ERROR; + } + report_pg_error(MOT::RC_ERROR, NULL); // execution control ends, calls ereport(error,...) } - // when running under thread-pool, it is possible to be executed from a thread that hasn't yet executed any MOT code - // and thus lacking a thread id and numa node id. Nevertheless, we can be sure that a proper session context is set - // up. - EnsureSafeThreadAccess(); + if (!IsJitSubContextInline(jitContext)) { + // Ensure that MOT FDW routine and Xact callbacks are registered. + if (!u_sess->mot_cxt.callbacks_set) { + ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false); + if (fdw != NULL) { + (void)GetFdwRoutine(fdw->fdwhandler); + } + } + + // Ensure all MOT thread/session-local identifiers are in place. + PrepareSessionAccess(); + } // make sure we have a valid transaction object (independent of MOT FDW) u_sess->mot_cxt.jit_txn = u_sess->mot_cxt.txn_manager; - if (u_sess->mot_cxt.jit_txn == NULL) { + if (u_sess->mot_cxt.jit_txn == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Execute JIT", "Cannot execute jitted code: Current transaction is undefined. Aborting transaction."); JitStatisticsProvider::GetInstance().AddFailExecQuery(); - report_pg_error(MOT::RC_MEMORY_ALLOCATION_ERROR); // execution control ends, calls ereport(error,...) + if (IsJitSubContextInline(jitContext)) { + return MOT::RC_MEMORY_ALLOCATION_ERROR; + } + report_pg_error(MOT::RC_MEMORY_ALLOCATION_ERROR, NULL); // execution control ends, calls ereport + } + + if (u_sess->mot_cxt.jit_txn->IsTxnAborted()) { + raiseAbortTxnError(); + } + + return MOT::RC_OK; +} + +static int JitPrepareExec(MotJitContext* jitContext) +{ + if (!IsJitSubContextInline(jitContext)) { + MOT_LOG_TRACE("Executing JIT context %p with query/function: %s", jitContext, jitContext->m_queryString); + } else { + MOT_LOG_DEBUG("Executing JIT context %p with query/function: %s", jitContext, jitContext->m_queryString); + } + + MOT::RC result = ValidatePrepare(jitContext); + if (result != MOT::RC_OK) { + return result; } // during the very first invocation of the query we need to setup the reusable search keys // This is also true after TRUNCATE TABLE, in which case we also need to re-fetch all index objects - if ((jitContext->m_argIsNull == nullptr) || - ((jitContext->m_commandType != JIT_COMMAND_INSERT) && (jitContext->m_index == nullptr))) { + bool needPrepare = ((jitContext->m_execState == nullptr) || MOT_ATOMIC_LOAD(jitContext->m_execState->m_purged)); + if (needPrepare) { + u_sess->mot_cxt.jit_codegen_error = 0; if (!PrepareJitContext(jitContext)) { + int errorCode = MOT_ERROR_OOM; + if (u_sess->mot_cxt.jit_codegen_error == ERRCODE_QUERY_CANCELED) { + errorCode = MOT_ERROR_STATEMENT_CANCELED; + } else { + int rootErrorCode = MOT::GetRootError(); + if (rootErrorCode != MOT_NO_ERROR) { + MOT::RC rootRC = MOT::ErrorToRC(rootErrorCode); + MOT_LOG_TRACE("Root error is %d %s, translated to %d %s", + rootErrorCode, + MOT::ErrorCodeToString(rootErrorCode), + rootRC, + MOT::RcToString(rootRC)); + errorCode = rootErrorCode; + } + } + result = MOT::ErrorToRC(errorCode); MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Execute JIT", "Failed to prepare for executing jitted code, aborting transaction"); + errorCode, "Execute JIT", "Failed to prepare for executing jitted code, aborting transaction"); JitStatisticsProvider::GetInstance().AddFailExecQuery(); - report_pg_error(MOT::RC_MEMORY_ALLOCATION_ERROR); // execution control ends, calls ereport(error,...) + if (IsJitSubContextInline(jitContext)) { + return result; + } + report_pg_error(result, NULL); // execution control ends, calls ereport } } - // setup current JIT context - u_sess->mot_cxt.jit_context = jitContext; + if (!IsJitSubContextInline(jitContext)) { + u_sess->mot_cxt.jit_txn->SetIsolationLevel(u_sess->utils_cxt.XactIsoLevel); + } + + // each query should start with m_rc = OK to avoid reporting wrong query result due to previous run + jitContext->m_rc = MOT::RC_OK; + + return MOT::RC_OK; +} #ifdef MOT_JIT_DEBUG - // in trace log-level we raise the log level to DEBUG on first few executions only - bool firstExec = false; - if ((++jitContext->m_execCount <= 2) && MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - MOT_LOG_TRACE("Executing JIT context %p (exec: %" PRIu64 ", query: %" PRIu64 ", iteration: %" PRIu64 "): %s", +#define MOT_JIT_DEBUG_EXEC_COUNT 2 + +static bool DebugPrintQueryExecStats(MotJitContext* jitContext, JitQueryExecState* execState) +{ + bool debugExec = false; + if ((++execState->m_execCount <= MOT_JIT_DEBUG_EXEC_COUNT) && MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + MOT_LOG_TRACE("Executing JIT query context %p (exec: %" PRIu64 ", query: %" PRIu64 ", iteration: %" PRIu64 + "): %s", jitContext, - jitContext->m_execCount, - jitContext->m_queryCount, - jitContext->m_iterCount, + execState->m_execCount, + execState->m_queryCount, + execState->m_iterCount, jitContext->m_queryString); - MOT::SetLogComponentLogLevel("JitExec", MOT::LogLevel::LL_DEBUG); - firstExec = true; + debugExec = true; } + return debugExec; +} + +static bool DebugPrintFunctionExecStats(MotJitContext* jitContext, JitFunctionExecState* execState) +{ + bool debugExec = false; + if ((++execState->m_execCount <= MOT_JIT_DEBUG_EXEC_COUNT) && MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + MOT_LOG_TRACE("Executing JIT function context %p (exec: %" PRIu64 "): %s", + jitContext, + execState->m_execCount, + jitContext->m_queryString); + debugExec = true; + } + return debugExec; +} #endif - // update iteration count and identify a new scan - int newScan = 0; - if (jitContext->m_iterCount == 0) { - ++jitContext->m_queryCount; - newScan = 1; -#ifdef MOT_JIT_DEBUG - MOT_LOG_TRACE("Starting a new scan (exec: %" PRIu64 ", query: %" PRIu64 ",iteration: %" PRIu64 ") for query %s", - jitContext->m_execCount, - jitContext->m_queryCount, - jitContext->m_iterCount, - jitContext->m_queryString); -#endif - } - ++jitContext->m_iterCount; - - // invoke the jitted function - if (jitContext->m_llvmFunction != nullptr) { -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Executing LLVM-jitted function %p: %s", jitContext->m_llvmFunction, jitContext->m_queryString); -#endif - result = ((JitFunc)jitContext->m_llvmFunction)(jitContext->m_table, - jitContext->m_index, - jitContext->m_searchKey, - jitContext->m_bitmapSet, - params, - slot, - tuplesProcessed, - scanEnded, - newScan, - jitContext->m_endIteratorKey, - jitContext->m_innerTable, - jitContext->m_innerIndex, - jitContext->m_innerSearchKey, - jitContext->m_innerEndIteratorKey); +#ifdef MOT_JIT_TEST +static void UpdateTestStats(MotJitContext* jitContext, int newScan) +{ + if (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + if (newScan) { + MOT_ATOMIC_INC(totalQueryExecCount); + MOT_LOG_DEBUG("Updated query count to %" PRIu64 ": %s", totalQueryExecCount, jitContext->m_queryString); + } else { + MOT_LOG_DEBUG("Skipped updated query count, not a new scan: %s", jitContext->m_queryString); + } } else { -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Executing TVM-jitted function %p: %s", jitContext->m_tvmFunction, jitContext->m_queryString); -#endif - result = JitExecTvmQuery(jitContext, params, slot, tuplesProcessed, scanEnded, newScan); + MOT_ATOMIC_INC(totalFunctionExecCount); + MOT_LOG_DEBUG("Updated function count to %" PRIu64 ": %s", totalFunctionExecCount, jitContext->m_queryString); } +} +#endif #ifdef MOT_JIT_DEBUG - if (firstExec) { - MOT::SetLogComponentLogLevel("JitExec", MOT::LogLevel::LL_TRACE); +static void JitExecWrapUp(MotJitContext* jitContext, MotJitContext* prevContext, int result, int newScan, + bool debugExec, MOT::LogLevel prevLevel) +#else +static void JitExecWrapUp(MotJitContext* jitContext, MotJitContext* prevContext, int result, int newScan) +#endif +{ +#ifdef MOT_JIT_DEBUG + if (debugExec && !IsJitSubContextInline(jitContext)) { + (void)MOT::SetLogComponentLogLevel("JitExec", prevLevel); } #endif - // reset current JIT context - u_sess->mot_cxt.jit_context = NULL; + // restore previous JIT context + if (prevContext) { + // pull up error information + prevContext->m_execState->m_nullColumnId = u_sess->mot_cxt.jit_context->m_execState->m_nullColumnId; + prevContext->m_execState->m_nullViolationTable = u_sess->mot_cxt.jit_context->m_execState->m_nullViolationTable; + } + u_sess->mot_cxt.jit_context = prevContext; + + if (result == 0 && jitContext->m_rc != MOT::RC_OK) { + // in case query result is OK, we need to verify that no error was raised while processing it. + MOT_LOG_TRACE( + "JitExecWrapUp(): setting result to (%s), jitContext->m_rc = %u, result = %u, jitContext = %p, query: (%s)", + RcToString(jitContext->m_rc), + jitContext->m_rc, + result, + jitContext, + jitContext->m_queryString); + result = jitContext->m_rc; + jitContext->m_rc = MOT::RC_OK; + } if (result == 0) { + // update statistics JitStatisticsProvider::GetInstance().AddInvokeQuery(); - if (newScan) { #ifdef MOT_JIT_TEST - MOT_ATOMIC_INC(totalExecCount); - MOT_LOG_INFO("JIT total queries executed: %" PRIu64, MOT_ATOMIC_LOAD(totalExecCount)); + UpdateTestStats(jitContext, newScan); #endif - JitStatisticsProvider::GetInstance().AddExecQuery(); - } + JitStatisticsProvider::GetInstance().AddExecQuery(); } else { ProcessJitResult((MOT::RC)result, jitContext, newScan); } +} + +#ifdef MOT_JIT_DEBUG +static void DebugPrintParams(ParamListInfo params, JitCommandType commandType) +{ + int paramCount = params ? params->numParams : 0; + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + MOT_LOG_DEBUG("Executing %s command with %d parameters: ", CommandToString(commandType), paramCount); + for (int i = 0; i < paramCount; ++i) { + if (!params->params[i].isnull) { + MOT_LOG_BEGIN(MOT::LogLevel::LL_DEBUG, "Param %d: ", i); + PrintDatum(MOT::LogLevel::LL_DEBUG, + params->params[i].ptype, + params->params[i].value, + params->params[i].isnull); + MOT_LOG_END(MOT::LogLevel::LL_DEBUG); + } + } + } +} +#define DEBUG_PRINT_PARAMS(params, commandType) DebugPrintParams(params, commandType) +#else +#define DEBUG_PRINT_PARAMS(params, commandType) +#endif + +static inline int JitExecGetNextTuple(JitQueryContext* jitContext, ParamListInfo params, TupleTableSlot* slot, + uint64_t* tuplesProcessed, int* scanEnded, int newScan) +{ + int result = MOT::RC_OK; + + if (jitContext->m_llvmFunction != nullptr) { +#ifdef MOT_JIT_DEBUG + MOT_LOG_DEBUG("JitExecGetNextTuple(): Executing LLVM-jitted function %p: %s", + jitContext->m_llvmFunction, + jitContext->m_queryString); +#endif + result = JitExecLlvmQuery(jitContext, params, slot, tuplesProcessed, scanEnded, newScan); + } else { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Cannot execute LLVM function, function is missing: %s", jitContext->m_queryString))); + } return result; } -extern void PurgeJitSourceCache(uint64_t relationId, bool purgeOnly) +static inline int JitExecCreateNonNativeTupleSort(MotJitContext* jitContext, + JitNonNativeSortExecState* jitNonNativeSortExecState, ParamListInfo params, TupleTableSlot* slot) { - MOT_LOG_TRACE("Purging JIT source map by relation id %" PRIu64, relationId); - (void)PurgeJitSourceMap(relationId, purgeOnly); + JitQueryContext* queryContext = (JitQueryContext*)jitContext; + JitNonNativeSortParams* sortParams = queryContext->m_nonNativeSortParams; + + int64 sortMem = SET_NODEMEM(0, 0); + int64 maxMem = 0; // No limit + + // Iterator's direction is consistent over single query + bool randomAccess = false; + + // Slot for my result tuples + TupleDesc tupDesc = slot->tts_tupleDescriptor; + + if (!tupDesc) { + MOT_LOG_TRACE("JitExecCreateNonNativeTupleSort(): Tuple descriptor (from slot) is NULL. slot: (%p)", slot); + MOT_LOG_ERROR("Tuple descriptor is invalid"); + + return MOT::RC_ERROR; + } + + // The parallel num of plan. Not relvant for MOT as this stage + int dop = SET_DOP(0); + + // Session memory context is used (instead of query context) as the allocated tupleSortState might be released only + // when the jit context is destroyed (In case the user didnt pull all results. e.g. In batch mode, the user can stop + // the query in the middle). In this case, when using query memory context, we will try to free\destroy a memory + // that was already freed + MemoryContext oldCxt = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); + Tuplesortstate* tupleSortState = tuplesort_begin_heap(tupDesc, + sortParams->numCols, + sortParams->sortColIdx, + sortParams->sortOperators, + sortParams->collations, + sortParams->nullsFirst, + sortMem, + randomAccess, + maxMem, + sortParams->plan_node_id, + dop); + + (void)MemoryContextSwitchTo(oldCxt); + if (sortParams->bound) { + tuplesort_set_bound(tupleSortState, sortParams->bound); + } + + // local variables for retrieving all tuples + int scanEnded = 0; + uint64_t tuplesProcessed = 0; + int newScan = 1; + int result = MOT::RC_OK; + uint64_t numRetrievedTuples = 0; // For debug only + + // Scan the index and feed all the tuples to tuplesort + while (true) { + tuplesProcessed = 0; + result = JitExecGetNextTuple(queryContext, params, slot, &tuplesProcessed, &scanEnded, newScan); + + if (scanEnded) { + ExecClearTuple(slot); + MOT_LOG_DEBUG( + "JitExecCreateNonNativeTupleSort(): Scan has ended. Total tuples received: %lu", numRetrievedTuples); + break; + } + + if (result != MOT::RC_OK || tuplesProcessed == 0) { + MOT_LOG_TRACE("JitExecCreateNonNativeTupleSort(): Failed to retrieve all tuples. Tuples retrieved: %lu, " + "l_tuplesProcessed: %lu, jitContext->m_llvmFunction: %p, query: %s", + numRetrievedTuples, + tuplesProcessed, + jitContext->m_llvmFunction, + jitContext->m_queryString); + MOT_LOG_ERROR("Failed to retrieve all tuples. Tuples retrieved: %lu, query: %s", + numRetrievedTuples, + (jitContext->m_queryString ? jitContext->m_queryString : "null")); + + ExecClearTuple(slot); + tuplesort_end(tupleSortState); + tupleSortState = nullptr; + // this is an error state. MOT::RC_LOCAL_ROW_NOT_FOUND might not interpreted as error, so we need to + // replace it with MOT::RC_ERROR. + return (result == MOT::RC_LOCAL_ROW_NOT_FOUND ? MOT::RC_ERROR : result); + } + + // if PGXC is defined, tuplesort_puttupleslotontape is used in some case (instead of tuplesort_puttupleslot). + // We will need to address this issue in the future. + tuplesort_puttupleslot(tupleSortState, slot); + numRetrievedTuples++; + newScan = 0; + } + + sort_count(tupleSortState); + + // Complete the sort + tuplesort_performsort(tupleSortState); + + jitNonNativeSortExecState->m_tupleSort = tupleSortState; + + return MOT::RC_OK; +} + +static inline bool JitExecOrderedQueryIsLimitReached( + JitNonNativeSortParams* sortParams, JitQueryExecState* jitQueryExecState) +{ + if (sortParams->bound && jitQueryExecState->m_limitCounter >= (uint32_t)sortParams->bound) { + return true; + } + + return false; +} + +static int JitExecOrderedQuery(MotJitContext* jitContext, JitQueryExecState* jitQueryExecState, ParamListInfo params, + TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded, int newScan) +{ + JitNonNativeSortExecState* jitNonNativeSortExecState = jitQueryExecState->m_nonNativeSortExecState; + JitQueryContext* queryContext = (JitQueryContext*)jitContext; + JitNonNativeSortParams* sortParams = queryContext->m_nonNativeSortParams; + + MOT_ASSERT(jitNonNativeSortExecState); + + if (newScan) { + MOT_LOG_TRACE("JitExecOrderedQuery(): Initializing JitNonNativeSortExecState for new sort for query: (%s)", + jitContext->m_queryString); + + // If tupleSortState already exists, we need to re-init the sort state + if (jitNonNativeSortExecState->m_tupleSort) { + tuplesort_end(jitNonNativeSortExecState->m_tupleSort); + jitNonNativeSortExecState->m_tupleSort = nullptr; + } + + int result = JitExecCreateNonNativeTupleSort(jitContext, jitNonNativeSortExecState, params, slot); + if (result != MOT::RC_OK) { + MOT_LOG_ERROR("Failed to create Tuple sort state for non native sort"); + return result; + } + + jitQueryExecState->m_limitCounter = 0; + } + + if (!jitNonNativeSortExecState->m_tupleSort) { + // This is not a new scan (newScan == 0) and we dont have a tupleSortState probably because we already destroyed + // it after setting *scanEnded = 1 + MOT_LOG_ERROR("Tuple was requested after scanEnded flag was raised. newScan %d", newScan); + *scanEnded = 1; + *tuplesProcessed = 0; + return MOT::RC_LOCAL_ROW_NOT_FOUND; + } + + // If limit clause, enforce number of returned tuples before get next tuple from tuplesort. + // Update scanEnded if last tuple was already provided + ExecClearTuple(slot); + if (JitExecOrderedQueryIsLimitReached(sortParams, jitQueryExecState) || + !(tuplesort_gettupleslot( + jitNonNativeSortExecState->m_tupleSort, ScanDirectionIsForward(sortParams->scanDir), slot, NULL))) { + MOT_LOG_TRACE("JitExecOrderedQuery(): limit reached or tuple sort is empty"); + tuplesort_end(jitNonNativeSortExecState->m_tupleSort); + jitNonNativeSortExecState->m_tupleSort = nullptr; + *scanEnded = 1; + *tuplesProcessed = 0; + return MOT::RC_LOCAL_ROW_NOT_FOUND; + } + + MOT_LOG_DEBUG("JitExecOrderedQuery(): returning next tuple from tuple sort"); + *tuplesProcessed = 1; + *scanEnded = 0; + if (sortParams->bound) { + // Increase tuple counter to support limit clause + jitQueryExecState->m_limitCounter++; + } + + return MOT::RC_OK; +} + +extern int JitExecQuery( + MotJitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded) +{ + MOT_ASSERT(jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY); + + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitQueryContext* queryContext = (JitQueryContext*)jitContext; + + volatile int result = JitPrepareExec(jitContext); + if (result != MOT::RC_OK) { + return result; + } + + // setup current JIT context + volatile MotJitContext* prevContext = u_sess->mot_cxt.jit_context; + u_sess->mot_cxt.jit_context = jitContext; + + // we avoid weird stuff by putting null in case of internal error + bool isQueryContext = (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY); + JitQueryExecState* execState = isQueryContext ? (JitQueryExecState*)jitContext->m_execState : nullptr; + MOT_ASSERT(execState); +#ifdef MOT_JIT_DEBUG + // in trace log-level we raise the log level to DEBUG on first few executions only + volatile MOT::LogLevel prevLevel = MOT::LogLevel::LL_INFO; + volatile bool debugExec = DebugPrintQueryExecStats(jitContext, execState); + if (debugExec) { + prevLevel = MOT::SetLogComponentLogLevel("JitExec", MOT::LogLevel::LL_DEBUG); + } +#endif + + // update iteration count and identify a new scan + volatile int newScan = 0; + if (execState->m_iterCount == 0) { + ++execState->m_queryCount; + newScan = 1; +#ifdef MOT_JIT_DEBUG + MOT_LOG_TRACE("Starting a new scan (exec: %" PRIu64 ", query: %" PRIu64 ", iter: %" PRIu64 ") for query %s", + execState->m_execCount, + execState->m_queryCount, + execState->m_iterCount, + jitContext->m_queryString); +#endif + } + ++execState->m_iterCount; + + // prepare invoke query parameters (invoked stored procedure reports results directly into caller's variables) + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + execState->m_directParams = params; + execState->m_invokeSlot = slot; + execState->m_invokeTuplesProcessed = tuplesProcessed; + execState->m_invokeScanEnded = scanEnded; + } + DEBUG_PRINT_PARAMS(params, jitContext->m_commandType); + + // clear error stack before new execution + if (!IsJitSubContextInline(jitContext)) { + MOT::ClearErrorStack(); + } + + // reset error state before new execution + ResetErrorState(execState); + + // invoke the jitted function + volatile MemoryContext origCxt = CurrentMemoryContext; + + // init out params + *scanEnded = 0; + *tuplesProcessed = 0; + + PG_TRY(); + { + if (queryContext->m_commandType != JIT_COMMAND_INVOKE) { + MOT_ASSERT(queryContext->m_commandType != JIT_COMMAND_FUNCTION); + } + if (IsWriteCommand(queryContext->m_commandType)) { + (void)::GetCurrentTransactionId(); + } + if (queryContext->m_nonNativeSortParams) { + result = JitExecOrderedQuery(jitContext, execState, params, slot, tuplesProcessed, scanEnded, newScan); + } else { + result = + JitExecGetNextTuple((JitQueryContext*)queryContext, params, slot, tuplesProcessed, scanEnded, newScan); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while executing jitted query '%s': %s", queryContext->m_queryString, edata->message); + ereport(WARNING, + (errmsg("Failed to execute MOT-jitted query '%s'.", queryContext->m_queryString), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + result = (int)MOT::RC_JIT_SP_EXCEPTION; + } + PG_END_TRY(); + +#ifdef MOT_JIT_DEBUG + JitExecWrapUp((MotJitContext*)jitContext, (MotJitContext*)prevContext, result, newScan, debugExec, prevLevel); +#else + JitExecWrapUp((MotJitContext*)jitContext, (MotJitContext*)prevContext, result, newScan); +#endif + return result; +} + +static void CopyTupleTableSlot(TupleTableSlot* tuple) +{ + // switch to memory context of caller + MemoryContext oldCtx = SwitchToSPICallerContext(); + + // copy slot values to memory context of caller + TupleDesc tupDesc = tuple->tts_tupleDescriptor; + for (int i = 0; i < tupDesc->natts; ++i) { + // skip dropped columns in destination + if (tupDesc->attrs[i]->attisdropped) { + continue; + } + + bool isNull = tuple->tts_isnull[i]; + Datum value = tuple->tts_values[i]; + Oid type = tuple->tts_tupleDescriptor->attrs[i]->atttypid; + + // perform proper type conversion as in exec_assign_value() at pl_exec.cpp + MOT_LOG_DEBUG("CopyTupleTableSlot(): Copying datum %d of type %u", i, type); + Datum resValue = CopyDatum(value, type, isNull); + tuple->tts_values[i] = resValue; + } + + // restore current memory context + if (oldCtx) { + (void)MemoryContextSwitchTo(oldCtx); + } +} + +extern int JitExecFunction( + MotJitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + MOT_ASSERT(jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + volatile JitFunctionContext* functionContext = (JitFunctionContext*)jitContext; + + volatile int result = JitPrepareExec(jitContext); + if (result != MOT::RC_OK) { + return result; + } + + // setup current JIT context + volatile MotJitContext* prevContext = u_sess->mot_cxt.jit_context; + u_sess->mot_cxt.jit_context = jitContext; + + // we avoid weird stuff by putting null in case of internal error + bool isFunctionContext = (jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + volatile JitFunctionExecState* execState = + isFunctionContext ? (JitFunctionExecState*)jitContext->m_execState : nullptr; + MOT_ASSERT(execState); + execState->m_spiBlockId = 0; // reset each time before execution +#ifdef MOT_JIT_DEBUG + // in trace log-level we raise the log level to DEBUG on first few executions only + volatile MOT::LogLevel prevLevel = MOT::LogLevel::LL_INFO; + volatile bool debugExec = DebugPrintFunctionExecStats(jitContext, (JitFunctionExecState*)execState); + if (debugExec) { + prevLevel = MOT::SetLogComponentLogLevel("JitExec", MOT::LogLevel::LL_DEBUG); + } +#endif + + // save parameters with which the function was invoked so they can later be pushed down to sub-queries + execState->m_functionParams = params; + + // reset error state + ResetErrorState((JitExec::JitExecState*)execState); + execState->m_exceptionOrigin = JIT_EXCEPTION_INTERNAL; + + // clear error stack before new execution + if (!IsJitSubContextInline(jitContext)) { + MOT::ClearErrorStack(); + } + DEBUG_PRINT_PARAMS(params, jitContext->m_commandType); + + // connect to SPI + SPIAutoConnect spiAutoConn(false, functionContext->m_functionOid); + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_ERROR("SPI_connect failed with error code %d: %s when executing MOT jitted function %s.", + rc, + SPI_result_code_string(rc), + jitContext->m_queryString); + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("SPI_connect failed with error code %d: %s when executing MOT jitted function %s.", + rc, + SPI_result_code_string(rc), + jitContext->m_queryString))); + } + + // invoke the jitted function + volatile MemoryContext origCxt = CurrentMemoryContext; + // since we use parts of the PG function in query invocation, we need to make sure it does not get deleted + ++execState->m_function->use_count; + MOT_LOG_DEBUG("JitExecFunction(): Increased use count of function %p to %lu: %s", + execState->m_function, + execState->m_function->use_count, + jitContext->m_queryString); + PG_TRY(); + { + if (jitContext->m_llvmSPFunction != nullptr) { +#ifdef MOT_JIT_DEBUG + MOT_LOG_DEBUG( + "Executing LLVM-jitted function %p: %s", jitContext->m_llvmSPFunction, jitContext->m_queryString); +#endif + result = + JitExecLlvmFunction((JitFunctionContext*)functionContext, params, slot, tuplesProcessed, scanEnded); + } else { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Cannot execute LLVM function, function is missing: %s", jitContext->m_queryString))); + } + + if (result == MOT::RC_OK) { + // copy tuple to caller's memory context + CopyTupleTableSlot(slot); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while executing jitted function '%s': %s", + functionContext->m_queryString, + edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Failed to execute MOT-jitted stored procedure '%s'.", functionContext->m_queryString), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + + // clean up: roll back all open sub-transactions, and open SPI connections + JitReleaseAllSubTransactions(true, InvalidSubTransactionId); + while (execState->m_spiBlockId > 0) { + JitCleanupBlockAfterException(); + } + result = (int)MOT::RC_ERROR; + } + PG_END_TRY(); + --execState->m_function->use_count; + MOT_LOG_DEBUG("JitExecFunction(): Decreased use count of function %p to %lu: %s", + execState->m_function, + execState->m_function->use_count, + jitContext->m_queryString); + + // disconnect from SPI - this includes handling any error and intermediate SPI state + spiAutoConn.Disconnect(); + +#ifdef MOT_JIT_DEBUG + JitExecWrapUp((MotJitContext*)jitContext, (MotJitContext*)prevContext, result, 1, debugExec, prevLevel); +#else + JitExecWrapUp((MotJitContext*)jitContext, (MotJitContext*)prevContext, result, 1); +#endif + return result; +} + +extern void PurgeJitSourceCache( + uint64_t objectId, JitPurgeScope purgeScope, JitPurgeAction purgeAction, const char* funcName) +{ + // since this might be called through initdb, we make a safety check here + if (MOT::MOTEngine::GetInstance() == nullptr) { + return; + } + + // ensure all MOT thread/session-local identifiers are in place + PrepareSessionAccess(); + + MOT_LOG_TRACE("Purging JIT source map by object id %" PRIu64 " scope: %s, action: %s", + objectId, + JitPurgeScopeToString(purgeScope), + JitPurgeActionToString(purgeAction)); + PurgeJitSourceMap(objectId, purgeScope, purgeAction, funcName); +} + +extern MotJitDetail* MOTGetJitDetail(uint32_t* num) +{ + *num = 0; + if (!IsMotCodegenEnabled()) { + return nullptr; + } + + // ensure all MOT thread/session-local identifiers are in place + PrepareSessionAccess(); + + MOT_LOG_DEBUG("Getting mot_jit_detail() information"); + return GetJitSourceDetail(num); +} + +extern MotJitProfile* MOTGetJitProfile(uint32_t* num) +{ + *num = 0; + if (!IsMotCodegenEnabled() || !MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + return nullptr; + } + + // ensure all MOT thread/session-local identifiers are in place + PrepareSessionAccess(); + + MOT_LOG_DEBUG("Getting mot_jit_profile() information"); + return JitProfiler::GetInstance()->GetProfileReport(num); +} + +class JitQueryRegenerator : public JitFunctionQueryVisitor { +public: + JitQueryRegenerator( + PLpgSQL_function* function, JitFunctionContext* functionContext, FuncParamInfoList* paramInfoList) + : m_function(function), m_functionContext(functionContext), m_paramInfoList(paramInfoList) + {} + + ~JitQueryRegenerator() final + {} + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) final + { + // 1. find corresponding sub-query context + // 2. check if it is invalid + // 3. regenerate code if required + JitCallSite* callSite = nullptr; + uint32_t subQueryIndex = (uint32_t)-1; + for (uint32_t i = 0; i < m_functionContext->m_SPSubQueryCount; ++i) { + if (m_functionContext->m_SPSubQueryList[i].m_exprIndex == index) { + callSite = &m_functionContext->m_SPSubQueryList[i]; + subQueryIndex = i; + MOT_LOG_TRACE( + "Found expression in sub-query %u/%u: %s", i, m_functionContext->m_SPSubQueryCount, expr->query); + break; + } + } + if (callSite == nullptr) { + MOT_LOG_TRACE("Expression %d at %p to be regenerated not found: %s", index, expr, expr->query); + return JitVisitResult::JIT_VISIT_CONTINUE; // go on, that's fine + } + MOT_ASSERT(subQueryIndex != (uint32_t)-1); + MotJitContext* subQueryContext = callSite->m_queryContext; + if (subQueryContext == nullptr) { + if (strcmp(expr->query, callSite->m_queryString) != 0) { + MOT_LOG_TRACE("Mismatching non-jittable sub-query %u: %s", subQueryIndex, expr->query); + return JitVisitResult::JIT_VISIT_ERROR; + } + return JitVisitResult::JIT_VISIT_CONTINUE; + } + if ((subQueryContext->m_queryString != nullptr) && (strcmp(expr->query, subQueryContext->m_queryString) != 0)) { + MOT_LOG_TRACE("Mismatching jittable sub-query %u: %s", subQueryIndex, expr->query); + return JitVisitResult::JIT_VISIT_ERROR; + } + uint8_t validState = MOT_ATOMIC_LOAD(subQueryContext->m_validState); + if (validState == JIT_CONTEXT_VALID) { + return JitVisitResult::JIT_VISIT_CONTINUE; + } else if (validState == JIT_CONTEXT_PENDING_COMPILE) { + MOT_LOG_TRACE("Encountered dummy sub-query %u pending for compilation: %s", subQueryIndex, expr->query); + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + // in either one of these cases we regenerate code for query: + // 1. if query itself was invalidated + // 2. if this is an invoke query with directly invalid sub-SP + bool regenCode = false; + if (validState & JIT_CONTEXT_INVALID) { + MOT_LOG_TRACE("Regenerating invalid sub-query: %s", subQueryContext->m_queryString); + regenCode = true; + } else if (validState & JIT_CONTEXT_DEPRECATE) { + MOT_LOG_TRACE("Regenerating deprecate sub-query: %s", subQueryContext->m_queryString); + regenCode = true; + } else if (validState & JIT_CONTEXT_CHILD_SP_INVALID) { + MOT_LOG_TRACE("Inspecting sub-query with invalid sub-SP: %s", subQueryContext->m_queryString); + MOT_ASSERT(subQueryContext->m_commandType == JIT_COMMAND_INVOKE); + MotJitContext* invokedContext = ((JitQueryContext*)subQueryContext)->m_invokeContext; + if (invokedContext == nullptr) { + MOT_LOG_TRACE("Regenerating sub-query with null sub-SP: %s", subQueryContext->m_queryString); + regenCode = true; + } else if (MOT_ATOMIC_LOAD(invokedContext->m_validState) & JIT_CONTEXT_INVALID) { + MOT_LOG_TRACE("Regenerating sub-query with invalid sub-SP: %s", subQueryContext->m_queryString); + regenCode = true; + } + } + if (regenCode) { + // we now generate code as usual + MOT_LOG_TRACE("Regenerating code for invalid sub-query: %s", expr->query); + if (!RegenerateStmtQuery(expr, + row, + m_paramInfoList, + m_function, + callSite, + m_functionContext->m_SPSubQueryCount, + subQueryIndex)) { + MOT_LOG_TRACE("Failed to regenerate sub-query: %s", expr->query); + return JitVisitResult::JIT_VISIT_ERROR; + } + // reinstate parent context (careful, this might be a non-jittable sub-query) + if (callSite->m_queryContext != nullptr) { + callSite->m_queryContext->m_parentContext = m_functionContext; + } + // NOTE: JIT Source will be updated upon first access (during PREPARE) + } else { + // this must be an invoke context and the invoked child SP has some sub-SP/query that is invalid, + // so we just revalidate it + MOT_LOG_TRACE("Re-validating code for sub-query %u with invalid sub-SP: %s", subQueryIndex, expr->query); + if (!RevalidateJitContext(subQueryContext)) { + MOT_LOG_TRACE("Failed to revalidate sub-query %u: %s", subQueryIndex, expr->query); + return JitVisitResult::JIT_VISIT_ERROR; + } + } + + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + void OnError(const char* stmtType, int lineNo) final + { + MOT_LOG_TRACE("Failed to regenerate SP query plans, during statement '%s' at line %d", stmtType, lineNo); + } + +private: + PLpgSQL_function* m_function; + JitFunctionContext* m_functionContext; + FuncParamInfoList* m_paramInfoList; +}; + +extern bool JitReCodegenFunctionQueries(MotJitContext* jitContext) +{ + // 1. get PG compiled function object + // 2. traverse all sub-queries + // 3. match the corresponding sub-query context + // 4. regenerate code if marked as invalid (as in first time compile) + // 5. invalid child SPs are wrapped with query INVOKE context, so they are handled transparently + + // connect to SPI outside try-catch block (since it uses longjmp - no destructors called) + SPIAutoConnect spiAutoConnect(true); + + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitFunctionContext* functionContext = (JitFunctionContext*)jitContext; + volatile FuncParamInfoList paramInfoList = {}; + enum class CodeRegenState { CRS_INIT, CRS_FUNC_PARAM, CRS_NS_PUSH, CRS_NS_POP }; + volatile CodeRegenState regenState = CodeRegenState::CRS_INIT; + volatile bool result = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile PLpgSQL_function* function = nullptr; + PG_TRY(); + { + function = GetPGCompiledFunction(functionContext->m_functionOid); + if (function == nullptr) { + MOT_LOG_TRACE( + "Cannot regenerate code for function: Failed to get function by id %u", functionContext->m_functionOid); + result = false; + } else { + // prepare execution state + ++function->use_count; + MOT_LOG_TRACE("JitReCodegenFunctionQueries(): Increased use count of function %p to %lu: %s", + function, + function->use_count, + jitContext->m_queryString); + + if (spiAutoConnect.Connect()) { + // prepare function parameters for query parsing + if (!PrepareFuncParamInfoList((PLpgSQL_function*)function, (FuncParamInfoList*)¶mInfoList)) { + MOT_LOG_TRACE("Cannot regenerate code for function: Failed to prepare function %u parameters", + functionContext->m_functionOid); + result = false; + } else { + regenState = CodeRegenState::CRS_FUNC_PARAM; + + // regenerate code for all sub-queries + JitQueryRegenerator codeRegen((PLpgSQL_function*)function, + (JitFunctionContext*)functionContext, + (FuncParamInfoList*)¶mInfoList); + + if (PushJitSourceNamespace(functionContext->m_functionOid, functionContext->m_queryString)) { + regenState = CodeRegenState::CRS_NS_PUSH; + result = VisitFunctionQueries((PLpgSQL_function*)function, &codeRegen); + PopJitSourceNamespace(); + regenState = CodeRegenState::CRS_NS_POP; + } + } + } else { + int rc = spiAutoConnect.GetErrorCode(); + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_UNAVAILABLE, + "JIT Compile", + "Cannot re-generate code for SP sub-queries: Failed to connect to SPI - %s (error code %d)", + SPI_result_code_string(rc), + rc); + result = false; + } + } + } + PG_CATCH(); + { + MOT_LOG_WARN("Caught exception while regenerating code for jitted function sub-queries"); + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while regenerating code for sub-queries of jitted function '%s': %s", + functionContext->m_queryString, + edata->message); + ereport(WARNING, + (errmsg("Failed to regenerate code for MOT-jitted stored procedure '%s'.", functionContext->m_queryString), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + result = false; + } + PG_END_TRY(); + + switch (regenState) { + case CodeRegenState::CRS_NS_PUSH: + PopJitSourceNamespace(); // fall through + case CodeRegenState::CRS_FUNC_PARAM: + DestroyFuncParamInfoList((FuncParamInfoList*)¶mInfoList); + break; + case CodeRegenState::CRS_NS_POP: + default: + break; + } + if (function != nullptr) { + --function->use_count; + MOT_LOG_TRACE("JitReCodegenFunctionQueries(): Decreased use count of function %p to %lu: %s", + function, + function->use_count, + jitContext->m_queryString); + } + + if (!result) { + MOT_LOG_TRACE("Cannot regenerate code for function: Failed to regenerate code for sub-query of function %u", + functionContext->m_functionOid); + } + + return result; +} + +extern bool TryRevalidateJitContext(MotJitContext* jitContext, TransactionId functionTxnId /* = InvalidTransactionId */) +{ + // Ensure that MOT FDW routine and Xact callbacks are registered. + if (!u_sess->mot_cxt.callbacks_set) { + ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false); + if (fdw != NULL) { + (void)GetFdwRoutine(fdw->fdwhandler); + } + } + + // Make sure session is ready for access + PrepareSessionAccess(); + + // make sure SPI is set-up on top level call when revalidate takes place with invoke context + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + SPIAutoConnect spiAutoConn; + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while generating code for query: %s (%u)", + jitContext->m_queryString, + SPI_result_code_string(rc), + rc); + MarkJitContextErrorCompile(jitContext); + return false; + } + + return RevalidateJitContext(jitContext, functionTxnId); + } else { + return RevalidateJitContext(jitContext, functionTxnId); + } +} + +static void JitDDLCallback(uint64_t relationId, MOT::DDLAccessType event, MOT::TxnDDLPhase txnDdlPhase) +{ + switch (event) { + case MOT::DDL_ACCESS_DROP_TABLE: + case MOT::DDL_ACCESS_TRUNCATE_TABLE: + case MOT::DDL_ACCESS_DROP_INDEX: + case MOT::DDL_ACCESS_ADD_COLUMN: + case MOT::DDL_ACCESS_DROP_COLUMN: + case MOT::DDL_ACCESS_RENAME_COLUMN: + PurgeJitSourceCache(relationId, JIT_PURGE_SCOPE_QUERY, JIT_PURGE_EXPIRE, nullptr); + break; + + case MOT::DDL_ACCESS_CREATE_TABLE: + case MOT::DDL_ACCESS_CREATE_INDEX: + PurgeJitSourceCache(relationId, JIT_PURGE_SCOPE_QUERY, JIT_PURGE_EXPIRE, nullptr); + break; + + case MOT::DDL_ACCESS_UNKNOWN: + if (txnDdlPhase == MOT::TxnDDLPhase::TXN_DDL_PHASE_COMMIT) { + ApplyLocalJitSourceChanges(); + } else if (txnDdlPhase == MOT::TxnDDLPhase::TXN_DDL_PHASE_ROLLBACK) { + RevertLocalJitSourceChanges(); + } else if (txnDdlPhase == MOT::TxnDDLPhase::TXN_DDL_PHASE_POST_COMMIT_CLEANUP) { + PostCommitCleanupJitSources(); + } + break; + + default: + MOT_LOG_TRACE("Invalid DDL event: %d", event); + break; + } +} + +inline const char* XactEventToString(XactEvent event) +{ + switch (event) { + case XACT_EVENT_START: + return "START"; + case XACT_EVENT_COMMIT: + return "COMMIT"; + case XACT_EVENT_END_TRANSACTION: + return "END TXN"; + case XACT_EVENT_RECORD_COMMIT: + return "RECORD COMMIT"; + case XACT_EVENT_ABORT: + return "ABORT"; + case XACT_EVENT_PREPARE: + return "PREPARE"; + case XACT_EVENT_COMMIT_PREPARED: + return "COMMIT PREPARED"; + case XACT_EVENT_ROLLBACK_PREPARED: + return "ROLLBACK PREPARED"; + case XACT_EVENT_PREROLLBACK_CLEANUP: + return "PRE-ROLLBACK CLEANUP"; + case XACT_EVENT_POST_COMMIT_CLEANUP: + return "POST-COMMIT CLEANUP"; + default: + return "N/A"; + } +} + +static void JITXactCallback(XactEvent event, void* arg) +{ + if ((MOT::MOTEngine::GetInstance() != nullptr) && IsMotCodegenEnabled()) { + MOT_LOG_DEBUG("Received transaction call back: %u (%s)", (int)event, XactEventToString(event)); + if (event == XACT_EVENT_COMMIT) { + JitDDLCallback(0, MOT::DDL_ACCESS_UNKNOWN, MOT::TxnDDLPhase::TXN_DDL_PHASE_COMMIT); + } else if (event == XACT_EVENT_ABORT) { + JitDDLCallback(0, MOT::DDL_ACCESS_UNKNOWN, MOT::TxnDDLPhase::TXN_DDL_PHASE_ROLLBACK); + } else if (event == XACT_EVENT_POST_COMMIT_CLEANUP) { + JitDDLCallback(0, MOT::DDL_ACCESS_UNKNOWN, MOT::TxnDDLPhase::TXN_DDL_PHASE_POST_COMMIT_CLEANUP); + } + } } extern bool JitInitialize() @@ -496,20 +2123,18 @@ extern bool JitInitialize() } if (JitCanInitThreadCodeGen()) { - if (IsMotPseudoCodegenForced()) { - MOT_LOG_INFO("Forcing TVM on LLVM natively supported platform by user configuration"); - g_instance.mot_cxt.jitExecMode = JIT_EXEC_MODE_TVM; - } else { - PrintNativeLlvmStartupInfo(); - g_instance.mot_cxt.jitExecMode = JIT_EXEC_MODE_LLVM; - } + PrintNativeLlvmStartupInfo(); } else { - if (IsMotPseudoCodegenForced()) { - MOT_LOG_INFO("Forcing TVM on LLVM natively unsupported platform by user configuration"); - } else { - MOT_LOG_WARN("Defaulting to TVM on LLVM natively unsupported platform"); + // whatever happened, we are done and we allow db to continue + return true; + } + + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + MOT_LOG_INFO("MOT JIT profiling is enabled"); + if (!JitProfiler::CreateInstance()) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "JIT Initialization", "Failed to create JIT profiler instance"); + return false; } - g_instance.mot_cxt.jitExecMode = JIT_EXEC_MODE_TVM; } enum InitState { JIT_INIT, JIT_CTX_POOL_INIT, JIT_SRC_POOL_INIT, JIT_INIT_DONE } initState = JIT_INIT; @@ -548,6 +2173,10 @@ extern bool JitInitialize() GetMotCodegenLimit()); break; } + MOT::MOTEngine::GetInstance()->SetDDLCallback(JitDDLCallback); + // when no DDL was issued, but only SP REPLACE/DROP, we are still missing commit/rollback notification, so we + // need to register a callback for that. This takes place in each session (see PrepareSessionAccess() above). + initState = JIT_INIT_DONE; } while (0); @@ -560,6 +2189,10 @@ extern bool JitInitialize() DestroyGlobalJitContextPool(); // fall through case JIT_INIT: + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + JitProfiler::DestroyInstance(); + } + // fall through default: break; } @@ -569,9 +2202,13 @@ extern bool JitInitialize() extern void JitDestroy() { + MOT::MOTEngine::GetInstance()->SetDDLCallback(nullptr); DestroyJitSourceMap(); DestroyJitSourcePool(); DestroyGlobalJitContextPool(); + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + JitProfiler::DestroyInstance(); + } } extern bool IsMotCodegenEnabled() @@ -579,9 +2216,14 @@ extern bool IsMotCodegenEnabled() return MOT::GetGlobalConfiguration().m_enableCodegen; } -extern bool IsMotPseudoCodegenForced() +extern bool IsMotQueryCodegenEnabled() { - return MOT::GetGlobalConfiguration().m_forcePseudoCodegen; + return IsMotCodegenEnabled() && MOT::GetGlobalConfiguration().m_enableQueryCodegen; +} + +extern bool IsMotSPCodegenEnabled() +{ + return IsMotCodegenEnabled() && MOT::GetGlobalConfiguration().m_enableSPCodegen; } extern bool IsMotCodegenPrintEnabled() @@ -593,4 +2235,65 @@ extern uint32_t GetMotCodegenLimit() { return MOT::GetGlobalConfiguration().m_codegenLimit; } + +extern void JitReportParseError(ErrorData* edata, const char* queryString) +{ + MOT_LOG_WARN("Encountered parse error: %s\n\tWhile parsing query: %s", edata->message, queryString); + if (u_sess->mot_cxt.jit_parse_error == 0) { + u_sess->mot_cxt.jit_parse_error = MOT_JIT_GENERIC_PARSE_ERROR; + } +} + +extern void CleanupJitSourceTxnState() +{ + if (MOT::MOTEngine::GetInstance() != nullptr) { + CleanupLocalJitSourceChanges(); + } + if (u_sess->mot_cxt.jit_xact_callback_registered) { + MOT_LOG_DEBUG("Unregistering transaction callback for current session"); + UnregisterXactCallback(JITXactCallback, nullptr); + u_sess->mot_cxt.jit_xact_callback_registered = false; + } +} + +extern void ForceJitContextInvalidation(MotJitContext* jitContext) +{ + // Here we don't have necessary locks, because the plan was just invalidated and about to be refreshed (See the + // caller RevalidateCachedQuery). We should not purge here without locks (purge tries to free the keys, etc., but + // another DDL can happen in parallel). All the purge should have happened already during the DDL operation. + InvalidateJitContext(jitContext, 0, JIT_CONTEXT_INVALID); +} + +extern bool IsInvokeQueryPlan(CachedPlanSource* planSource, Oid* functionOid, TransactionId* functionTxnId) +{ + if (planSource->query_list == nullptr) { + return false; + } + + if (list_length(planSource->query_list) != 1) { + return false; + } + + Query* query = (Query*)linitial(planSource->query_list); + FuncExpr* funcExpr = GetFuncExpr(query); + if (funcExpr == nullptr) { + return false; + } + + Oid funcId = funcExpr->funcid; + TransactionId funcXmin = InvalidTransactionId; + HeapTuple procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcId)); + if (HeapTupleIsValid(procTuple)) { + funcXmin = HeapTupleGetRawXmin(procTuple); + } + ReleaseSysCache(procTuple); + + if (functionOid != nullptr) { + *functionOid = funcId; + } + if (functionTxnId != nullptr) { + *functionTxnId = funcXmin; + } + return true; +} } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_explain.cpp b/src/gausskernel/storage/mot/jit_exec/jit_explain.cpp index 16e488eba..9e49f3da4 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_explain.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_explain.cpp @@ -27,6 +27,9 @@ #include "jit_common.h" #include "mot_engine.h" #include "utilities.h" +#include "utils/timestamp.h" +#include "utils/lsyscache.h" +#include "mot_internal.h" namespace JitExec { DECLARE_LOGGER(JitExplain, JitExec) @@ -38,75 +41,24 @@ static void ExplainRangeSelectPlan(Query* query, JitRangeSelectPlan* plan, bool static void ExplainConstExpr(JitConstExpr* expr) { - MOT_ASSERT(expr->_source_expr->type == T_Const); - Const* constExpr = (Const*)expr->_source_expr; - switch (constExpr->consttype) { - case BOOLOID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[bool] %u", (unsigned)DatumGetBool(constExpr->constvalue)); - break; - - case CHAROID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[char] %u", (unsigned)DatumGetChar(constExpr->constvalue)); - break; - - case INT1OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[int1] %u", (unsigned)DatumGetUInt8(constExpr->constvalue)); - break; - - case INT2OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[int2] %u", (unsigned)DatumGetUInt16(constExpr->constvalue)); - break; - - case INT4OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[int4] %u", (unsigned)DatumGetUInt32(constExpr->constvalue)); - break; - - case INT8OID: - MOT_LOG_APPEND( - MOT::LogLevel::LL_TRACE, "[int8] %u" PRIu64, (uint64_t)DatumGetUInt64(constExpr->constvalue)); - break; - - case TIMESTAMPOID: - MOT_LOG_APPEND( - MOT::LogLevel::LL_TRACE, "[timestamp] %" PRIu64, (uint64_t)DatumGetTimestamp(constExpr->constvalue)); - break; - - case FLOAT4OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[float4] %f", (double)DatumGetFloat4(constExpr->constvalue)); - break; - - case FLOAT8OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[float8] %f", (double)DatumGetFloat8(constExpr->constvalue)); - break; - - case VARCHAROID: { - VarChar* vc = DatumGetVarCharPP(constExpr->constvalue); - int size = VARSIZE_ANY_EXHDR(vc); - char* src = VARDATA_ANY(vc); - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[varchar] %.*s", size, src); - } break; - - case NUMERICOID: { - Datum result = DirectFunctionCall1(numeric_out, constExpr->constvalue); - char* cstring = DatumGetCString(result); - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[numeric] %s", cstring); - } break; - - default: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, - "[type %d] %" PRIu64, - (int)constExpr->consttype, - (uint64_t)constExpr->constvalue); - break; + if (expr->_is_null) { // special case of default parameters + PrintDatum(MOT::LogLevel::LL_TRACE, expr->_const_type, PointerGetDatum(nullptr), true); + } else { + MOT_ASSERT(expr->_source_expr != nullptr); + MOT_ASSERT(expr->_source_expr->type == T_Const); + Const* constExpr = (Const*)expr->_source_expr; + PrintDatum(MOT::LogLevel::LL_TRACE, constExpr->consttype, constExpr->constvalue, constExpr->constisnull); } } static void ExplainRealColumnName(const Query* query, int tableRefId, int columnId) { + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); if (tableRefId <= list_length(query->rtable)) { // varno index is 1-based RangeTblEntry* rte = (RangeTblEntry*)list_nth(query->rtable, tableRefId - 1); if (rte->rtekind == RTE_RELATION) { - MOT::Table* table = MOT::GetTableManager()->GetTableByExternal(rte->relid); + MOT::Table* table = currTxn->GetTableByExternalId(rte->relid); if (table != nullptr) { if (list_length(query->rtable) == 1) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "%s", table->GetFieldName(columnId)); @@ -121,7 +73,7 @@ static void ExplainRealColumnName(const Query* query, int tableRefId, int column tableRefId = aliasVar->varno; if (tableRefId <= list_length(query->rtable)) { // tableRefId is 1-based rte = (RangeTblEntry*)list_nth(query->rtable, tableRefId - 1); - MOT::Table* table = MOT::GetTableManager()->GetTableByExternal(rte->relid); + MOT::Table* table = currTxn->GetTableByExternalId(rte->relid); if (table != nullptr) { // take real column id and not column id from virtual join table columnId = aliasVar->varattno; @@ -177,68 +129,36 @@ static void ExplainParamExpr(JitParamExpr* expr) } } -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, #name "("); \ - ExplainExpr(query, plan, expr->_args[0]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); \ - break; +static void ExplainPGFunction(Query* query, JitPlan* plan, Oid functionId, JitExpr** args, int argCount) +{ + // get PG function name + char* functionName = get_func_name(functionId); + if (functionName != nullptr) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "<%s %u>(", functionName, (unsigned)functionId); + } else { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "(", (unsigned)functionId); + } + pfree(functionName); -#define APPLY_BINARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, #name "("); \ - ExplainExpr(query, plan, expr->_args[0]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); \ - ExplainExpr(query, plan, expr->_args[1]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, #name "("); \ - ExplainExpr(query, plan, expr->_args[0]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); \ - ExplainExpr(query, plan, expr->_args[1]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); \ - ExplainExpr(query, plan, expr->_args[2]); \ - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) + for (int i = 0; i < argCount; ++i) { + ExplainExpr(query, plan, args[i]); + if ((i + 1) < argCount) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + } + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); +} static void ExplainOpExpr(Query* query, JitPlan* plan, JitOpExpr* expr) { - // explain the operator - switch (expr->_op_func_id) { - APPLY_OPERATORS() - - default: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[op_%u]", expr->_op_func_id); - break; - } + ExplainPGFunction(query, plan, expr->_op_func_id, expr->_args, expr->_arg_count); } static void ExplainFuncExpr(Query* query, JitPlan* plan, JitFuncExpr* expr) { - // explain the function - switch (expr->_func_id) { - APPLY_OPERATORS() - - default: - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[func_%d]", expr->_func_id); - break; - } + ExplainPGFunction(query, plan, expr->_func_id, expr->_args, expr->_arg_count); } -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - static void ExplainSubLinkExpr(Query* query, JitPlan* plan, JitSubLinkExpr* subLinkExpr) { // currently we support only one sub-query (so we don't use sub-query plan index) @@ -261,6 +181,25 @@ static void ExplainSubLinkExpr(Query* query, JitPlan* plan, JitSubLinkExpr* subL } } +static void ExplainScalarArrayOpExpr(Query* query, JitPlan* plan, JitScalarArrayOpExpr* expr) +{ + ExplainExpr(query, plan, expr->m_scalar); + if (expr->m_useOr) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " IN ANY "); + } else { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " IN ALL "); + } + + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "["); + for (int i = 0; i < expr->m_arraySize; ++i) { + ExplainExpr(query, plan, expr->m_arrayElements[i]); + if ((i + 1) < expr->m_arraySize) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + } + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "]"); +} + static void ExplainExpr(Query* query, JitPlan* plan, JitExpr* expr) { switch (expr->_expr_type) { @@ -288,6 +227,10 @@ static void ExplainExpr(Query* query, JitPlan* plan, JitExpr* expr) ExplainSubLinkExpr(query, plan, (JitSubLinkExpr*)expr); break; + case JIT_EXPR_TYPE_SCALAR_ARRAY_OP: + ExplainScalarArrayOpExpr(query, plan, (JitScalarArrayOpExpr*)expr); + break; + default: MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "[expr]"); break; @@ -312,19 +255,24 @@ static const char* JitWhereOperatorClassToString(JitWhereOperatorClass opClass) } } -static void ExplainFilterArray( - Query* query, JitPlan* plan, int indent, JitFilterArray* filterArray, bool isSubQuery = false) +static void ExplainFilterArray(Query* query, JitPlan* plan, int indent, JitFilterArray* filterArray, + bool isSubQuery = false, bool isOneTime = false) { if (!isSubQuery) { - MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "%*sFILTER ON (", indent, ""); + if (isOneTime) { + MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "%*sONE-TIME FILTER ON (", indent, ""); + } else { + MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "%*sFILTER ON (", indent, ""); + } } else { - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " FILTER ON ("); + if (isOneTime) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " FILTER ON ("); + } else { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " ONE-TIME FILTER ON ("); + } } for (int i = 0; i < filterArray->_filter_count; ++i) { - ExplainExpr(query, plan, filterArray->_scan_filters[i]._lhs_operand); - JitWhereOperatorClass opClass = ClassifyWhereOperator(filterArray->_scan_filters[i]._filter_op); - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " %s ", JitWhereOperatorClassToString(opClass)); - ExplainExpr(query, plan, filterArray->_scan_filters[i]._rhs_operand); + ExplainExpr(query, plan, filterArray->_scan_filters[i]); if (i < (filterArray->_filter_count - 1)) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " AND "); } @@ -372,7 +320,7 @@ static void ExplainSelectExprArray(Query* query, JitPlan* plan, JitSelectExprArr MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "("); for (int i = 0; i < exprArray->_count; ++i) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "%%%d = ", exprArray->_exprs[i]._tuple_column_id); - ExplainExpr(query, plan, (JitExpr*)exprArray->_exprs[i]._column_expr); + ExplainExpr(query, plan, (JitExpr*)exprArray->_exprs[i]._expr); if (i < (exprArray->_count - 1)) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); } @@ -435,6 +383,10 @@ static void ExplainPointQueryPlan(Query* query, JitPointQueryPlan* plan, bool is MOT_LOG_TRACE("[Plan] Point Query"); } int indent = 0; + if (plan->_query.m_oneTimeFilters._filter_count > 0) { + ExplainFilterArray(query, (JitPlan*)plan, indent, &plan->_query.m_oneTimeFilters, isSubQuery, true); + indent += 2; + } if (plan->_query._filters._filter_count > 0) { ExplainFilterArray(query, (JitPlan*)plan, indent, &plan->_query._filters, isSubQuery); indent += 2; @@ -556,11 +508,15 @@ static const char* JitIndexScanDirectionToString(JitIndexScanDirection scanDirec static void ExplainIndexScan(Query* query, JitPlan* plan, int indent, JitIndexScan* indexScan, const char* scanName = "", bool isSubQuery = false) { + if (indexScan->m_oneTimeFilters._filter_count > 0) { + ExplainFilterArray(query, plan, indent, &indexScan->m_oneTimeFilters, isSubQuery, true); + indent += 2; + } if (indexScan->_filters._filter_count > 0) { ExplainFilterArray(query, plan, indent, &indexScan->_filters, isSubQuery); indent += 2; } - MOT::Index* index = indexScan->_table->GetIndex(indexScan->_index_id); + MOT::Index* index = indexScan->_index; if (isSubQuery) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "%s%sSCAN %s index %s (%s, %s) ON (", @@ -633,10 +589,15 @@ static void ExplainAggregateOperator(int indent, const JitAggregate* aggregate, if (aggregate->_distinct) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "DISTINCT("); } - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, - "%s.%s)", - aggregate->_table->GetTableName().c_str(), - aggregate->_table->GetFieldName(aggregate->_table_column_id)); + if (aggregate->_table == nullptr) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "*)"); + } else { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, + "%s.%s type %d)", + aggregate->_table->GetTableName().c_str(), + aggregate->_table->GetFieldName(aggregate->_table_column_id), + aggregate->_element_type); + } if (aggregate->_distinct) { MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); } @@ -645,6 +606,46 @@ static void ExplainAggregateOperator(int indent, const JitAggregate* aggregate, } } +static void ExplainColumnName(Query* query, JitExpr* expr) +{ + if (expr->_expr_type == JIT_EXPR_TYPE_VAR) { + Var* varExpr = (Var*)expr->_source_expr; + int columnId = varExpr->varattno; + int tableRefId = varExpr->varno; + ExplainRealColumnName(query, tableRefId, columnId); + } +} + +static void ExplainRangeSelectSort(Query* query, JitRangeSelectPlan* plan, int indent) +{ + JitNonNativeSortParams* sortParams = plan->m_nonNativeSortParams; + MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "%*sSORT ", indent, ""); + if (ScanDirectionIsBackward(sortParams->scanDir)) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "DESC"); + } else if (ScanDirectionIsForward(sortParams->scanDir)) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "ASC"); + } + for (int i = 0; i < sortParams->numCols; ++i) { + AttrNumber colId = sortParams->sortColIdx[i]; + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " {Column %d [", colId); + ExplainColumnName(query, plan->_select_exprs._exprs[colId - 1]._expr); + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, + "], SortOp %u, Collation %u, nulls-first %s}", + sortParams->sortOperators[i], + sortParams->collations[i], + sortParams->nullsFirst[i] ? "true" : "false"); + if (i + 1 < plan->m_nonNativeSortParams->numCols) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ","); + } + } + if (plan->m_nonNativeSortParams->bound == 0) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " no bound"); + } else { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " bound %" PRIu64, plan->m_nonNativeSortParams->bound); + } + MOT_LOG_END(MOT::LogLevel::LL_TRACE); +} + static void ExplainRangeSelectPlan(Query* query, JitRangeSelectPlan* plan, bool isSubQuery /* = false */) { if (isSubQuery) { @@ -654,9 +655,13 @@ static void ExplainRangeSelectPlan(Query* query, JitRangeSelectPlan* plan, bool MOT_LOG_TRACE("[Plan] Range SELECT from table %s:", plan->_index_scan._table->GetTableName().c_str()); } int indent = 0; - if (plan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { + for (int aggIndex = 0; aggIndex < plan->m_aggCount; ++aggIndex) { indent += 2; - ExplainAggregateOperator(indent, &plan->_aggregate, isSubQuery); + ExplainAggregateOperator(indent, &plan->m_aggregates[aggIndex], isSubQuery); + if (aggIndex + 1 < plan->m_aggCount) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + indent -= 2; } if (plan->_limit_count > 0) { if (isSubQuery) { @@ -666,9 +671,13 @@ static void ExplainRangeSelectPlan(Query* query, JitRangeSelectPlan* plan, bool MOT_LOG_TRACE("%*sLIMIT %d", indent, "", plan->_limit_count); } } - if (plan->_aggregate._aggreaget_op == JIT_AGGREGATE_NONE) { + if (plan->m_nonNativeSortParams != nullptr) { + indent += 2; + ExplainRangeSelectSort(query, plan, indent); + } + if (plan->m_aggCount == 0) { if (isSubQuery) { - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " SELECT"); + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "%*sSELECT", indent, ""); ExplainSelectExprArray(query, (JitPlan*)plan, &plan->_select_exprs); } else { indent += 2; @@ -681,18 +690,43 @@ static void ExplainRangeSelectPlan(Query* query, JitRangeSelectPlan* plan, bool ExplainIndexScan(query, (JitPlan*)plan, indent + 2, &plan->_index_scan, "", isSubQuery); } +static void ExplainRangeDeletePlan(Query* query, JitRangeDeletePlan* plan) +{ + MOT_LOG_TRACE("[Plan] Range DELETE table %s:", plan->_index_scan._table->GetTableName().c_str()); + ExplainIndexScan(query, (JitPlan*)plan, 2, &plan->_index_scan); +} + static void ExplainRangeScanPlan(Query* query, JitRangeScanPlan* plan) { if (plan->_command_type == JIT_COMMAND_UPDATE) { ExplainRangeUpdatePlan(query, (JitRangeUpdatePlan*)plan); } else if (plan->_command_type == JIT_COMMAND_SELECT) { ExplainRangeSelectPlan(query, (JitRangeSelectPlan*)plan); + } else if (plan->_command_type == JIT_COMMAND_DELETE) { + ExplainRangeDeletePlan(query, (JitRangeDeletePlan*)plan); } else { MOT_LOG_TRACE("Invalid plan command type"); return; } } +static const char* JitJoinTypeToString(JitJoinType joinType) +{ + switch (joinType) { + case JitJoinType::JIT_JOIN_INNER: + return "INNER"; + case JitJoinType::JIT_JOIN_LEFT: + return "LEFT"; + case JitJoinType::JIT_JOIN_FULL: + return "FULL"; + case JitJoinType::JIT_JOIN_RIGHT: + return "RIGHT"; + case JitJoinType::JIT_JOIN_INVALID: + default: + return "Invalid"; + } +} + static const char* JitJoinScanTypeToString(JitJoinScanType scanType) { switch (scanType) { @@ -712,15 +746,21 @@ static const char* JitJoinScanTypeToString(JitJoinScanType scanType) static void ExplainJoinPlan(Query* query, JitJoinPlan* plan) { - MOT_LOG_TRACE("[Plan] JOIN table %s on table %s (%s)", + MOT_LOG_TRACE("[Plan] %s JOIN table %s on table %s (%s)", + JitJoinTypeToString(plan->_join_type), plan->_outer_scan._table->GetTableName().c_str(), plan->_inner_scan._table->GetTableName().c_str(), JitJoinScanTypeToString(plan->_scan_type)); int indent = 0; - if (plan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { + for (int aggIndex = 0; aggIndex < plan->m_aggCount; ++aggIndex) { indent += 2; - ExplainAggregateOperator(indent, &plan->_aggregate); - } else { + ExplainAggregateOperator(indent, &plan->m_aggregates[aggIndex]); + if (aggIndex + 1 < plan->m_aggCount) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + indent -= 2; + } + if (plan->m_aggCount == 0) { indent += 2; MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "%*sSELECT", indent, ""); ExplainSelectExprArray(query, (JitPlan*)plan, &plan->_select_exprs); @@ -749,6 +789,34 @@ static void ExplainCompoundPlan(Query* query, JitCompoundPlan* plan) ExplainSearchExprArray(query, (JitPlan*)plan, indent + 4, &plan->_outer_query_plan->_query._search_exprs, false); } +static void ExplainInvokePlan(Query* query, JitInvokePlan* plan) +{ + MOT_LOG_TRACE("[Plan] Invoke stored procedure:"); + MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, " %s[id=%u] (", plan->_function_name, plan->_function_id); + for (int i = 0; i < plan->_arg_count; ++i) { + ExplainExpr(query, (JitPlan*)plan, plan->_args[i]); + if (i < plan->_arg_count - 1) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + } + if ((plan->_arg_count > 0) && (plan->m_defaultParamCount > 0)) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + for (int i = 0; i < plan->m_defaultParamCount; ++i) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, " "); + if (plan->m_defaultParams[i] == nullptr) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "null"); + } else { + ExplainExpr(query, (JitPlan*)plan, plan->m_defaultParams[i]); + } + if (i < plan->m_defaultParamCount - 1) { + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ", "); + } + } + MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, ")"); + MOT_LOG_END(MOT::LogLevel::LL_TRACE); +} + extern void JitExplainPlan(Query* query, JitPlan* plan) { if (plan != nullptr) { @@ -773,10 +841,19 @@ extern void JitExplainPlan(Query* query, JitPlan* plan) ExplainCompoundPlan(query, (JitCompoundPlan*)plan); break; + case JIT_PLAN_INVOKE: + ExplainInvokePlan(query, (JitInvokePlan*)plan); + break; + case JIT_PLAN_INVALID: default: break; } } } + +extern void JitExplainPlan(PLpgSQL_function* function, JitPlan* plan) +{ + // not implemented yet +} } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_helpers.cpp b/src/gausskernel/storage/mot/jit_exec/jit_helpers.cpp index 2aa3fd7a3..10c1ebb09 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_helpers.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_helpers.cpp @@ -32,6 +32,14 @@ #include "access/xact.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "catalog/pg_type.h" +#include "fmgr.h" +#include "parser/parse_coerce.h" +#include "access/tupconvert.h" +#include "opfusion/opfusion.h" +#include "access/tableam.h" #include "jit_helpers.h" #include "jit_common.h" @@ -39,35 +47,120 @@ #include "utilities.h" #include -typedef std::unordered_set DistinctIntSetType; -typedef std::unordered_set DistinctDoubleSetType; +#include "storage/mot/mot_fdw.h" +#include "jit_llvm_util.h" +#include "jit_plan_sp.h" +#include "jit_profiler.h" +#include "jit_source.h" +#include "mot_fdw_helpers.h" DECLARE_LOGGER(LlvmHelpers, JitExec) +struct NumericEquals : public std::binary_function { + inline bool operator()(const Datum& lhs, const Datum& rhs) const + { + Datum res = DirectFunctionCall2(numeric_eq, lhs, rhs); + return DatumGetBool(res); + } +}; + +struct NumericHash : public std::unary_function { + inline size_t operator()(const Datum& arg) const + { + Numeric num = DatumGetNumeric(arg); + NumericDigit* digits = NUMERIC_DIGITS(num); + int ndigits = NUMERIC_NDIGITS(num); + int nweight = NUMERIC_WEIGHT(num); + int dscale = NUMERIC_DSCALE(num); + int nsign = NUMERIC_SIGN(num); + + size_t hashValue = 17; + hashValue = hashValue * 19 + ndigits; + hashValue = hashValue * 19 + nweight; + hashValue = hashValue * 19 + dscale; + hashValue = hashValue * 19 + nsign; + for (int i = 0; i < ndigits; ++i) { + hashValue = hashValue * 19 + digits[i]; + } + return hashValue; + } +}; + +struct VarcharEquals : public std::binary_function { + inline bool operator()(const Datum& lhs, const Datum& rhs) const + { + Datum res = DirectFunctionCall2(bpchareq, lhs, rhs); + return DatumGetBool(res); + } +}; + +struct VarcharHash : public std::unary_function { + inline size_t operator()(const Datum& arg) const + { + bytea* text = DatumGetByteaP(arg); + uint32_t size = VARSIZE(text); // includes header len VARHDRSZ + char* src = VARDATA(text); + uint32_t strSize = size - VARHDRSZ; + + // implement djb2 + size_t hashValue = 5381; + for (uint32_t i = 0; i < strSize; ++i) { + size_t c = (size_t)src[i]; + hashValue = ((hashValue << 5) + hashValue) + c; // hash * 33 + c + } + return hashValue; + } +}; + +typedef std::unordered_set DistinctIntSetType; +typedef std::unordered_set DistinctDoubleSetType; +using DistinctNumericSetType = std::unordered_set; +using DistinctVarcharSetType = std::unordered_set; + /** @brief Helper to prepare a numeric zero. */ static Datum makeNumericZero() { return DirectFunctionCall1(int4_numeric, Int32GetDatum(0)); } -/** @brief Convert numeric Datum to double precision value. */ -static double numericToDouble(Datum numeric_value) -{ - return (double)DatumGetFloat8(DirectFunctionCall1(numeric_float8, numeric_value)); -} - /** @brief Allocates and initializes a distinct set of integers. */ static void* prepareDistinctIntSet() { void* buf = MOT::MemSessionAlloc(sizeof(DistinctIntSetType)); - return new (buf) DistinctIntSetType(); + if (buf) { + buf = new (buf) DistinctIntSetType(); + } + return buf; } /** @brief Allocates and initializes a distinct set of double-precision values. */ static void* prepareDistinctDoubleSet() { void* buf = MOT::MemSessionAlloc(sizeof(DistinctDoubleSetType)); - return new (buf) DistinctDoubleSetType(); + if (buf) { + buf = new (buf) DistinctDoubleSetType(); + } + return buf; +} + +/** @brief Allocates and initializes a distinct set of numeric datum values. */ +static void* prepareDistinctNumericSet() +{ + void* buf = MOT::MemSessionAlloc(sizeof(DistinctNumericSetType)); + if (buf) { + buf = new (buf) DistinctNumericSetType(); + } + return buf; +} + +/** @brief Allocates and initializes a distinct set of varchar datum values. */ +static void* prepareDistinctVarcharSet() +{ + void* buf = MOT::MemSessionAlloc(sizeof(DistinctVarcharSetType)); + if (buf) { + buf = new (buf) DistinctVarcharSetType(); + } + return buf; } /** @brief Inserts an integer to a distinct set of integers. */ @@ -81,7 +174,7 @@ static int insertDistinctIntItem(void* distinct_set, int64_t item) return result; } -/** @brief Inserts an integer to a distinct set of double-precision values. */ +/** @brief Inserts a double-precision number to a distinct set of double-precision values. */ static int insertDistinctDoubleItem(void* distinct_set, double item) { int result = 0; @@ -92,230 +185,67 @@ static int insertDistinctDoubleItem(void* distinct_set, double item) return result; } +/** @brief Inserts a numeric to a distinct set of double-precision values. */ +static int insertDistinctNumericItem(void* distinctSet, Datum item) +{ + int result = 0; + DistinctNumericSetType* numericSet = (DistinctNumericSetType*)distinctSet; + if (numericSet->insert(item).second) { + result = 1; + } + return result; +} + +/** @brief Inserts a varchar to a distinct set of double-precision values. */ +static int insertDistinctVarcharItem(void* distinctSet, Datum item) +{ + int result = 0; + DistinctVarcharSetType* varcharSet = (DistinctVarcharSetType*)distinctSet; + if (varcharSet->insert(item).second) { + MOT_LOG_DEBUG("Varchar Item inserted"); + result = 1; + } + return result; +} + /** @brief Destroys and frees a distinct set of integers. */ static void destroyDistinctIntSet(void* distinct_set) { DistinctIntSetType* int_set = (DistinctIntSetType*)distinct_set; - int_set->~DistinctIntSetType(); - MOT::MemSessionFree(distinct_set); + if (int_set) { + int_set->~DistinctIntSetType(); + MOT::MemSessionFree(distinct_set); + } } /** @brief Destroys and frees a distinct set of double-precision values. */ static void destroyDistinctDoubleSet(void* distinct_set) { DistinctDoubleSetType* double_set = (DistinctDoubleSetType*)distinct_set; - double_set->~DistinctDoubleSetType(); - MOT::MemSessionFree(distinct_set); -} - -/*--------------------------- DEBUG Print Helpers ---------------------------*/ -#ifdef MOT_JIT_DEBUG -/** @brief Prints to log a numeric value. */ -static void dbg_print_numeric(const char* msg, Numeric num) -{ - NumericDigit* digits = NUMERIC_DIGITS(num); - int ndigits; - int i; - - ndigits = NUMERIC_NDIGITS(num); - - MOT_LOG_BEGIN(MOT::LogLevel::LL_DEBUG, "%s: NUMERIC w=%d d=%d ", msg, NUMERIC_WEIGHT(num), NUMERIC_DSCALE(num)); - switch (NUMERIC_SIGN(num)) { - case NUMERIC_POS: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "POS"); - break; - - case NUMERIC_NEG: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "NEG"); - break; - - case NUMERIC_NAN: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "NaN"); - break; - - default: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "SIGN=0x%x", NUMERIC_SIGN(num)); - break; - } - - for (i = 0; i < ndigits; i++) { - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, " %0*d", DEC_DIGITS, digits[i]); - } - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, " (%f)", numericToDouble(NumericGetDatum(num))); - MOT_LOG_END(MOT::LogLevel::LL_DEBUG); -} - -/** @brief Prints to log a varchar value. */ -static void dbg_print_varchar(const char* msg, VarChar* vc) -{ - size_t size = VARSIZE(vc); - char* src = VARDATA(vc); - MOT_LOG_DEBUG("%s: size=%u, data=%.*s", msg, (unsigned)size, (int)size, src); - size = VARSIZE_ANY_EXHDR(vc); - src = VARDATA_ANY(vc); - MOT_LOG_DEBUG("%s: [PG] size=%u, data=%.*s", msg, (unsigned)size, (int)size, src); - // NOTE: last printout looks better, make sure this is what gets into the row -} - -/** @var Prints to log a geneirc datum value. */ -static void dbg_print_datum(const char* msg, Oid ptype, Datum datum, bool isnull) -{ - if (isnull) { - MOT_LOG_DEBUG("[type %u] NULL", ptype); - } else if (ptype == NUMERICOID) { - dbg_print_numeric(msg, DatumGetNumeric(datum)); - } else if (ptype == VARCHAROID) { - dbg_print_varchar(msg, DatumGetVarCharPP(datum)); - } else { - MOT_LOG_BEGIN(MOT::LogLevel::LL_DEBUG, "%s: ", msg); - switch (ptype) { - case BOOLOID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[bool] %u", (unsigned)DatumGetBool(datum)); - break; - - case CHAROID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[char] %u", (unsigned)DatumGetChar(datum)); - break; - - case INT1OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[int1] %u", (unsigned)DatumGetUInt8(datum)); - break; - - case INT2OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[int2] %u", (unsigned)DatumGetUInt16(datum)); - break; - - case INT4OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[int4] %u", (unsigned)DatumGetUInt32(datum)); - break; - - case INT8OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[int8] %" PRIu64, (uint64_t)DatumGetUInt64(datum)); - break; - - case TIMESTAMPOID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[timestamp] %" PRIu64, (uint64_t)DatumGetTimestamp(datum)); - break; - - case FLOAT4OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[float4] %f", (double)DatumGetFloat4(datum)); - break; - - case FLOAT8OID: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[float8] %f", (double)DatumGetFloat8(datum)); - break; - - default: - MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "[type %u] %" PRIu64, ptype, (uint64_t)datum); - break; - } - - MOT_LOG_END(MOT::LogLevel::LL_DEBUG); + if (double_set) { + double_set->~DistinctDoubleSetType(); + MOT::MemSessionFree(distinct_set); } } -#endif -// helper debug printing macros -#ifdef MOT_JIT_DEBUG -#define DBG_PRINT_NUMERIC(msg, numeric) \ - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { \ - dbg_print_numeric(msg, numeric); \ - } -#else -#define DBG_PRINT_NUMERIC(msg, numeric) -#endif - -#ifdef MOT_JIT_DEBUG -#define DBG_PRINT_VARCHAR(msg, vc) \ - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { \ - dbg_print_varchar(msg, vc); \ - } -#else -#define DBG_PRINT_VARCHAR(msg, vc) -#endif - -#ifdef MOT_JIT_DEBUG -#define DBG_PRINT_DATUM(msg, ptype, datum, isnull) \ - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { \ - dbg_print_datum(msg, ptype, datum, isnull); \ - } -#else -#define DBG_PRINT_DATUM(msg, ptype, datum, isnull) -#endif - -static Oid column_type_to_pg(MOT::MOT_CATALOG_FIELD_TYPES column_type) +/** @brief Destroys and frees a distinct set of double-precision values. */ +static void destroyDistinctNumericSet(void* distinctSet) { - Oid pg_type = -1; - switch (column_type) { - case MOT::MOT_TYPE_DECIMAL: - pg_type = NUMERICOID; - break; - - case MOT::MOT_TYPE_VARCHAR: - pg_type = VARCHAROID; - break; - - case MOT::MOT_TYPE_CHAR: - pg_type = CHAROID; - break; - - case MOT::MOT_TYPE_TINY: - pg_type = INT1OID; - break; - - case MOT::MOT_TYPE_SHORT: - pg_type = INT2OID; - break; - - case MOT::MOT_TYPE_INT: - pg_type = INT4OID; - break; - - case MOT::MOT_TYPE_LONG: - pg_type = INT8OID; - break; - - case MOT::MOT_TYPE_FLOAT: - pg_type = FLOAT4OID; - break; - - case MOT::MOT_TYPE_DOUBLE: - pg_type = FLOAT8OID; - break; - - case MOT::MOT_TYPE_DATE: - pg_type = DATEOID; - break; - - case MOT::MOT_TYPE_TIME: - pg_type = TIMEOID; - break; - - case MOT::MOT_TYPE_TIMESTAMP: - pg_type = TIMESTAMPOID; - break; - - case MOT::MOT_TYPE_TIMESTAMPTZ: - pg_type = TIMESTAMPTZOID; - break; - - case MOT::MOT_TYPE_INTERVAL: - pg_type = INTERVALOID; - break; - - case MOT::MOT_TYPE_TIMETZ: - pg_type = TIMETZOID; - break; - - case MOT::MOT_TYPE_BLOB: - pg_type = BLOBOID; - break; - - default: - break; + DistinctNumericSetType* numericSet = (DistinctNumericSetType*)distinctSet; + if (numericSet) { + numericSet->~DistinctNumericSetType(); + MOT::MemSessionFree(numericSet); } +} - return pg_type; +/** @brief Destroys and frees a distinct set of varchar values. */ +static void destroyDistinctVarcharSet(void* distinctSet) +{ + DistinctVarcharSetType* varcharSet = (DistinctVarcharSetType*)distinctSet; + if (varcharSet) { + varcharSet->~DistinctVarcharSetType(); + MOT::MemSessionFree(varcharSet); + } } /*--------------------------- LLVM Access Helpers ---------------------------*/ @@ -325,6 +255,75 @@ void debugLog(const char* function, const char* msg) MOT_LOG_DEBUG("%s: %s", function, msg); } +void debugLogInt(const char* msg, int arg) +{ + MOT_LOG_DEBUG(msg, arg); +} + +void debugLogString(const char* msg, const char* arg) +{ + MOT_LOG_DEBUG(msg, arg); +} + +void debugLogStringDatum(const char* msg, int64_t arg) +{ + DEBUG_PRINT_DATUM(msg, VARCHAROID, (Datum)arg, false); +} + +void debugLogDatum(const char* msg, Datum value, int isNull, int type) +{ + DEBUG_PRINT_DATUM(msg, type, (Datum)value, isNull); +} + +static void RaiseLlvmFault(int faultCode) +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext); + jitContext->m_execState->m_faultCode = (uint64_t)faultCode; + MOT_LOG_DEBUG("RaiseLlvmFault, jumping to faultBuf at %p on exec state %p: %s", + (int8_t*)jitContext->m_execState->m_faultBuf, + jitContext->m_execState, + jitContext->m_queryString); + siglongjmp(jitContext->m_execState->m_faultBuf, faultCode); +} + +static void RaiseFault(int faultCode) +{ + RaiseLlvmFault(faultCode); +} + +inline void RaiseAccessViolationFault() +{ + RaiseLlvmFault(LLVM_FAULT_ACCESS_VIOLATION); +} + +inline void RaiseResourceLimitFault() +{ + RaiseLlvmFault(LLVM_FAULT_RESOURCE_LIMIT); +} + +inline void ValidatePointer(void* ptr, const char* file, int line, const char* msg) +{ + if (ptr == nullptr) { + MOT_LOG_ERROR("Invalid pointer accessed at %s, line %d: %s", file, line, msg); + RaiseAccessViolationFault(); + } +} + +inline void ValidateArray(int index, int arraySize, const char* file, int line, const char* msg) +{ + if (index >= arraySize) { + MOT_LOG_ERROR("Array index %d out of bounds %d at %s, line %d: %s", index, arraySize, file, line, msg); + RaiseAccessViolationFault(); + } +} + +/** @define Helper macro for validating pointer access. */ +#define VERIFY_PTR(ptr, msg) ValidatePointer(ptr, __FILE__, __LINE__, msg) + +/** @define Helper macro for validating array access. */ +#define VERIFY_ARRAY_ACCESS(index, arraySize, msg) ValidateArray(index, arraySize, __FILE__, __LINE__, msg) + /*--------------------------- Engine Access Helpers ---------------------------*/ int isSoftMemoryLimitReached() { @@ -362,7 +361,11 @@ MOT::Index* getTableIndex(MOT::Table* table, int index_id) void InitKey(MOT::Key* key, MOT::Index* index) { - MOT_LOG_DEBUG("Initializing key %p by source index %s (%p)", key, index->GetName().c_str(), index); + MOT_LOG_DEBUG("Initializing key %p by source index %s %u (%p)", + key, + index->GetName().c_str(), + (unsigned)index->GetExtId(), + index); key->InitKey(index->GetKeyLength()); MOT_LOG_DEBUG("key %p initialized to %u bytes", key, key->GetKeyLength()); } @@ -379,58 +382,96 @@ MOT::Column* getColumnAt(MOT::Table* table, int table_colid) return column; } -void setExprArgIsNull(int arg_pos, int isnull) +void SetExprIsNull(int isnull) { - MOT_LOG_DEBUG("Setting expression argument %d isnull to: %d", arg_pos, isnull); - u_sess->mot_cxt.jit_context->m_argIsNull[arg_pos] = isnull ? 1 : 0; + MOT_LOG_DEBUG("Setting expression isnull to: %d", isnull); + u_sess->mot_cxt.jit_context->m_execState->m_exprIsNull = isnull; } -int getExprArgIsNull(int arg_pos) +int GetExprIsNull() { - int result = u_sess->mot_cxt.jit_context->m_argIsNull[arg_pos]; - MOT_LOG_DEBUG("Retrieved expression argument %d isnull: %d", arg_pos, result); + int result = (int)u_sess->mot_cxt.jit_context->m_execState->m_exprIsNull; + MOT_LOG_DEBUG("Retrieved expression isnull: %d", result); return result; } -Datum GetConstAt(int constId, int argPos) +void SetExprCollation(int collationId) +{ + MOT_LOG_DEBUG("Setting expression collation to: %d", collationId); + u_sess->mot_cxt.jit_context->m_execState->m_exprCollationId = collationId; +} + +int GetExprCollation() +{ + int result = (int)u_sess->mot_cxt.jit_context->m_execState->m_exprCollationId; + MOT_LOG_DEBUG("Retrieved expression collation: %d", result); + return result; +} + +#ifdef MOT_JIT_DEBUG +#define GET_EXPR_IS_NULL() GetExprIsNull() +#define SET_EXPR_IS_NULL(isnull) SetExprIsNull(isnull) +#define GET_EXPR_COLLATION() GetExprCollation() +#define SET_EXPR_COLLATION(collation) SetExprCollation(collation) +#else +#define GET_EXPR_IS_NULL() (int)u_sess->mot_cxt.jit_context->m_execState->m_exprIsNull +#define SET_EXPR_IS_NULL(isnull) u_sess->mot_cxt.jit_context->m_execState->m_exprIsNull = (isnull) +#define GET_EXPR_COLLATION() (int)u_sess->mot_cxt.jit_context->m_execState->m_exprCollationId +#define SET_EXPR_COLLATION(collation) u_sess->mot_cxt.jit_context->m_execState->m_exprCollationId = (collation) +#endif + +Datum GetConstAt(int constId) { MOT_LOG_DEBUG("Retrieving constant datum by id %d", constId); Datum result = PointerGetDatum(nullptr); - JitExec::JitContext* ctx = u_sess->mot_cxt.jit_context; + JitExec::MotJitContext* ctx = u_sess->mot_cxt.jit_context; if (constId < (int)ctx->m_constDatums.m_datumCount) { JitExec::JitDatum* datum = &ctx->m_constDatums.m_datums[constId]; result = datum->m_datum; - setExprArgIsNull(argPos, datum->m_isNull); - DBG_PRINT_DATUM("Retrieved constant datum", datum->m_type, datum->m_datum, datum->m_isNull); + ctx->m_execState->m_exprIsNull = datum->m_isNull; + DEBUG_PRINT_DATUM("Retrieved constant datum", datum->m_type, datum->m_datum, datum->m_isNull); } else { MOT_LOG_ERROR("Invalid constant identifier: %d", constId); + RaiseAccessViolationFault(); } return result; } -Datum getDatumParam(ParamListInfo params, int paramid, int arg_pos) +Datum getDatumParam(ParamListInfo params, int paramid) { MOT_LOG_DEBUG("Retrieving datum param at index %d", paramid); - DBG_PRINT_DATUM( + DEBUG_PRINT_DATUM( "Param value", params->params[paramid].ptype, params->params[paramid].value, params->params[paramid].isnull); - setExprArgIsNull(arg_pos, params->params[paramid].isnull); + SET_EXPR_IS_NULL(params->params[paramid].isnull); return params->params[paramid].value; } -Datum readDatumColumn(MOT::Table* table, MOT::Row* row, int colid, int arg_pos) +Datum readDatumColumn(MOT::Table* table, MOT::Row* row, int colid, int innerRow, int subQueryIndex) { + // special case: row is null (can happen with left join) + if (row == nullptr) { + MOT_LOG_DEBUG("readDatumColumn(): Row is null (left join?)"); + SET_EXPR_IS_NULL(1); + return PointerGetDatum(NULL); // return proper NULL datum + } + MOT::Column* column = table->GetField(colid); - MOT_LOG_DEBUG("Reading Datum value from row %p column %p", row, column); + MOT_LOG_DEBUG("Reading Datum value from row %p column %p [%s] in table %p [%s]", + row, + column, + column->m_name, + table, + table->GetTableName().c_str()); Datum result = PointerGetDatum(NULL); // return proper NULL datum if column value is null FormData_pg_attribute attr; attr.attnum = colid; - attr.atttypid = column_type_to_pg(column->m_type); + attr.atttypid = ConvertMotColumnTypeToOid(column->m_type); bool isnull = false; MOTAdaptor::MOTToDatum(table, &attr, (uint8_t*)row->GetData(), &result, &isnull); - DBG_PRINT_DATUM("Column value", attr.atttypid, result, isnull); - setExprArgIsNull(arg_pos, (int)isnull); + DEBUG_PRINT_DATUM("Column value", attr.atttypid, result, isnull); + SET_EXPR_IS_NULL((int)isnull); return result; } @@ -438,14 +479,14 @@ Datum readDatumColumn(MOT::Table* table, MOT::Row* row, int colid, int arg_pos) void writeDatumColumn(MOT::Row* row, MOT::Column* column, Datum value) { MOT_LOG_DEBUG("Writing to row %p column %p datum value", row, column); - DBG_PRINT_DATUM("Datum value", column_type_to_pg(column->m_type), value, 0); - MOTAdaptor::DatumToMOT(column, value, column_type_to_pg(column->m_type), (uint8_t*)row->GetData()); + DEBUG_PRINT_DATUM("Datum value", ConvertMotColumnTypeToOid(column->m_type), value, 0); + MOTAdaptor::DatumToMOT(column, value, ConvertMotColumnTypeToOid(column->m_type), (uint8_t*)row->GetData()); } void buildDatumKey( MOT::Column* column, MOT::Key* key, Datum value, int index_colid, int offset, int size, int value_type) { - int isnull = getExprArgIsNull(0); + int isnull = GET_EXPR_IS_NULL(); MOT_LOG_DEBUG("buildKey: writing datum value for index column %d at key buf offset %d (col=%p, key=%p, datum=%p, " "value-type=%d, key-size=%u, col-size=%u, size=%d, is-null: %d)", index_colid, @@ -458,7 +499,7 @@ void buildDatumKey( (unsigned)column->m_size, size, isnull); - DBG_PRINT_DATUM("Key Datum", value_type, value, 0); + DEBUG_PRINT_DATUM("Key Datum", value_type, value, 0); if (isnull) { MOT_LOG_DEBUG("Setting null key datum at offset %d (%d bytes)", offset, size); errno_t erc = memset_s(key->GetKeyBuf() + offset, key->GetKeyLength() - offset, 0x00, size); @@ -467,82 +508,529 @@ void buildDatumKey( MOTAdaptor::DatumToMOTKey(column, (Oid)value_type, value, - column->m_envelopeType, + (Oid)column->m_envelopeType, key->GetKeyBuf() + offset, - column->m_size, + size, KEY_OPER::READ_KEY_EXACT, 0x00); } } /*--------------------------- Invoke PG Operators ---------------------------*/ -// cast operators retain first null parameter as null result -// other operator will crash if null is provided -#define APPLY_UNARY_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum arg, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking unary operator: " #name); \ - return DirectFunctionCall1(name, arg); \ - } - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum arg, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking unary cast operator: " #name); \ - int isnull = getExprArgIsNull(arg_pos); \ - return isnull ? arg : DirectFunctionCall1(name, arg); \ - } - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum lhs, Datum rhs, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking binary operator: " #name); \ - return DirectFunctionCall2(name, lhs, rhs); \ - } - -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum lhs, Datum rhs, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking binary cast operator: " #name); \ - int lhs_isnull = getExprArgIsNull(arg_pos); \ - return lhs_isnull ? lhs : DirectFunctionCall2(name, lhs, rhs); \ - } - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum arg1, Datum arg2, Datum arg3, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking ternary operator: " #name); \ - return DirectFunctionCall3(name, arg1, arg2, arg3); \ - } - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - Datum invoke_##name(Datum arg1, Datum arg2, Datum arg3, int arg_pos) \ - { \ - MOT_LOG_DEBUG("Invoking ternary operator: " #name); \ - int arg1_isnull = getExprArgIsNull(arg_pos); \ - return arg1_isnull ? arg1 : DirectFunctionCall3(name, arg1, arg2, arg3); \ - } - -APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -MOT::Row* searchRow(MOT::Table* table, MOT::Key* key, int access_mode_value) +static Datum MakeDatumString(const char* message) { - MOT_LOG_DEBUG("Searching row at table %p by key %p", table, key); + size_t strSize = strlen(message); + size_t allocSize = VARHDRSZ + strSize + 1; + bytea* copy = (bytea*)palloc(allocSize); + if (copy == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "JIT Execute", "Failed to allocate %u bytes for error datum string", (unsigned)allocSize); + return PointerGetDatum(nullptr); + } + + errno_t erc = memcpy_s(VARDATA(copy), strSize, (uint8_t*)message, strSize); + securec_check(erc, "\0", "\0"); + + SET_VARSIZE(copy, allocSize); + VARDATA(copy)[strSize] = 0; + return PointerGetDatum(copy); +} + +static MOT::RC HandlePGError(JitExec::JitExecState* execState, const char* operation, int spiConnectId = -1) +{ + volatile MOT::RC result = MOT::RC_ERROR; + + // first restore SPI state if ordered to do so + if (spiConnectId >= 0) { + SPI_disconnect(spiConnectId + 1); + SPI_restore_connection(); + } + + // now handle error + volatile ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while %s: %s", operation, edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while %s: %s", operation, edata->message), + errdetail("%s", edata->detail))); + + // prepare error data and SQL state in JIT execution state + // note: make sure all memory is allocated in caller's context + volatile MemoryContext origCxt = JitExec::SwitchToSPICallerContext(); + PG_TRY(); + { + execState->m_errorMessage = PointerGetDatum(nullptr); + execState->m_errorDetail = PointerGetDatum(nullptr); + execState->m_errorHint = PointerGetDatum(nullptr); + execState->m_sqlStateString = PointerGetDatum(nullptr); + + execState->m_errorMessage = MakeDatumString(edata->message); + if (edata->detail != 0) { + execState->m_errorDetail = MakeDatumString(edata->detail); + } + if (edata->hint != 0) { + execState->m_errorHint = MakeDatumString(edata->hint); + } + execState->m_sqlState = edata->sqlerrcode; + if (execState->m_sqlState == ERRCODE_IN_FAILED_SQL_TRANSACTION) { + result = MOT::RC_TXN_ABORTED; + } else if (execState->m_sqlState == 0) { + execState->m_sqlState = ERRCODE_PLPGSQL_ERROR; + } + char sqlStateCode[6] = {}; + JitExec::SqlStateToCode(execState->m_sqlState, sqlStateCode); + execState->m_sqlStateString = MakeDatumString(sqlStateCode); + } + PG_CATCH(); + { + MOT_LOG_PANIC("Failed to allocate memory in error handler"); + } + PG_END_TRY(); + (void)MemoryContextSwitchTo(origCxt); + + // cleanup error state + FlushErrorState(); + FreeErrorData((ErrorData*)edata); + return result; +} + +static void SetDefaultErrorData(JitExec::JitExecState* execState) +{ + // prepare error data and SQL state in JIT execution state + // note: make sure all memory is allocated in caller's context + volatile MemoryContext origCxt = JitExec::SwitchToSPICallerContext(); + PG_TRY(); + { + execState->m_errorMessage = PointerGetDatum(nullptr); + execState->m_errorDetail = PointerGetDatum(nullptr); + execState->m_errorHint = PointerGetDatum(nullptr); + execState->m_sqlStateString = PointerGetDatum(nullptr); + + execState->m_errorMessage = MakeDatumString("Unknown error occurred"); + execState->m_sqlState = ERRCODE_PLPGSQL_ERROR; + char sqlStateCode[6] = {}; + JitExec::SqlStateToCode(execState->m_sqlState, sqlStateCode); + execState->m_sqlStateString = MakeDatumString(sqlStateCode); + } + PG_CATCH(); + { + MOT_LOG_PANIC("Failed to allocate memory while setting default error data"); + } + PG_END_TRY(); + (void)MemoryContextSwitchTo(origCxt); +} + +inline JitExec::JitFunctionExecState* GetFunctionExecState() +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext != nullptr); + MOT_ASSERT(jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionExecState* execState = (JitExec::JitFunctionExecState*)jitContext->m_execState; + MOT_ASSERT(execState != nullptr); + return execState; +} + +static void SignalException(JitExec::MotJitContext* jitContext) +{ + if (jitContext->m_execState->m_sqlState == 0) { + SetDefaultErrorData(jitContext->m_execState); + } + + int faultCode = jitContext->m_execState->m_sqlState; + + MOT_LOG_DEBUG("SignalException, with faultBuf at %p on exec state %p: %s", + (int8_t*)jitContext->m_execState->m_faultBuf, + jitContext->m_execState, + jitContext->m_queryString); + + if (jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + if (execState->m_exceptionStack == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT LLVM Stored Procedure", + "Cannot signal exception: exception stack is empty, while executing %s. Aborting execution.", + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } + + // set exception value and raise exception status + execState->m_exceptionStatus = 1; + execState->m_exceptionValue = faultCode; + + // set exception origin + execState->m_exceptionOrigin = JIT_EXCEPTION_EXTERNAL; + } else { + JitExec::JitExecState* execState = (JitExec::JitExecState*)jitContext->m_execState; + // set exception value and raise exception status + execState->m_exceptionStatus = 1; + execState->m_exceptionValue = faultCode; + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } +} + +inline void HandlePGFunctionError(JitExec::MotJitContext* jitContext, int spiConnectId) +{ + (void)HandlePGError(jitContext->m_execState, "executing PG Function", spiConnectId); +} + +Datum JitInvokePGFunction0(PGFunction fptr, int collationId) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + FunctionCallInfoData fcinfo; + InitFunctionCallInfoData(fcinfo, NULL, 0, (Oid)collationId, NULL, NULL); + PG_TRY(); + { + result = fptr(&fcinfo); + SET_EXPR_IS_NULL(fcinfo.isnull); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +inline void PrepareFmgrInfo(FunctionCallInfoData* fcinfo, FmgrInfo* flinfo, FuncExpr* funcExpr, List* funcArgs, + ListCell* cells, Const* args, Oid* argTypes, int argCount) +{ + funcExpr->xpr.type = T_FuncExpr; + funcExpr->funcvariadic = false; + + // prepare for function arguments a list of constants nodes with corresponding argument types + for (int i = 0; i < argCount; ++i) { + fcinfo->argTypes[i] = argTypes[i]; + args[i].xpr.type = T_Const; + args[i].consttype = argTypes[i]; + cells[i].data.ptr_value = &args[i]; + if (i < argCount - 1) { + cells[i].next = &cells[i + 1]; + } else { + cells[i].next = nullptr; + } + } + + funcArgs->type = T_List; + funcArgs->length = argCount; + funcArgs->head = &cells[0]; + funcArgs->tail = &cells[argCount - 1]; + + funcExpr->args = funcArgs; + flinfo->fn_expr = (fmNodePtr)funcExpr; + fcinfo->flinfo = flinfo; +} + +Datum JitInvokePGFunction1(PGFunction fptr, int collationId, int isStrict, Datum arg, int isnull, Oid argType) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + FunctionCallInfoData fcinfo1; + InitFunctionCallInfoData(fcinfo1, NULL, 1, (Oid)collationId, NULL, NULL); + PG_TRY(); + { + bool shouldCallFunc = true; + fcinfo1.arg[0] = arg; + fcinfo1.argnull[0] = isnull; + + // some functions require types in flinfo.fn_expr, so we fake it as required + FmgrInfo flinfo = {}; + FuncExpr funcExpr = {}; + List funcArgs = {}; + ListCell cells[1] = {}; + Const args[1] = {}; + Oid argTypes[1] = {argType}; + PrepareFmgrInfo(&fcinfo1, &flinfo, &funcExpr, &funcArgs, cells, args, argTypes, 1); + + if (isStrict > 0) { + for (int i = 0; i < fcinfo1.nargs; i++) { + if (fcinfo1.argnull[i]) { + shouldCallFunc = false; + break; + } + } + } + + // call the function + if (shouldCallFunc) { + result = fptr(&fcinfo1); + SET_EXPR_IS_NULL(fcinfo1.isnull); + } else { + SET_EXPR_IS_NULL(true); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +Datum JitInvokePGFunction2(PGFunction fptr, int collationId, int isStrict, Datum arg1, int isnull1, Oid argType1, + Datum arg2, int isnull2, Oid argType2) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + FunctionCallInfoData fcinfo2; + InitFunctionCallInfoData(fcinfo2, NULL, 2, (Oid)collationId, NULL, NULL); + PG_TRY(); + { + bool shouldCallFunc = true; + fcinfo2.arg[0] = arg1; + fcinfo2.argnull[0] = isnull1; + fcinfo2.arg[1] = arg2; + fcinfo2.argnull[1] = isnull2; + + // some functions require types in flinfo.fn_expr, so we fake it as required + FmgrInfo flinfo = {}; + FuncExpr funcExpr = {}; + List funcArgs = {}; + ListCell cells[2] = {}; + Const args[2] = {}; + Oid argTypes[2] = {argType1, argType2}; + PrepareFmgrInfo(&fcinfo2, &flinfo, &funcExpr, &funcArgs, cells, args, argTypes, 2); + + if (isStrict > 0) { + for (int i = 0; i < fcinfo2.nargs; i++) { + if (fcinfo2.argnull[i]) { + shouldCallFunc = false; + break; + } + } + } + + // call the function + if (shouldCallFunc) { + result = fptr(&fcinfo2); + SET_EXPR_IS_NULL(fcinfo2.isnull); + } else { + SET_EXPR_IS_NULL(true); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +Datum JitInvokePGFunction3(PGFunction fptr, int collationId, int isStrict, Datum arg1, int isnull1, Oid argType1, + Datum arg2, int isnull2, Oid argType2, Datum arg3, int isnull3, Oid argType3) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + FunctionCallInfoData fcinfo3; + InitFunctionCallInfoData(fcinfo3, NULL, 3, (Oid)collationId, NULL, NULL); + PG_TRY(); + { + bool shouldCallFunc = true; + fcinfo3.arg[0] = arg1; + fcinfo3.argnull[0] = isnull1; + fcinfo3.arg[1] = arg2; + fcinfo3.argnull[1] = isnull2; + fcinfo3.arg[2] = arg3; + fcinfo3.argnull[2] = isnull3; + + // some functions require types in flinfo.fn_expr, so we fake it as required + FmgrInfo flinfo = {}; + FuncExpr funcExpr = {}; + List funcArgs = {}; + ListCell cells[3] = {}; + Const args[3] = {}; + Oid argTypes[3] = {argType1, argType2, argType3}; + PrepareFmgrInfo(&fcinfo3, &flinfo, &funcExpr, &funcArgs, cells, args, argTypes, 3); + + // call the function + if (isStrict > 0) { + for (int i = 0; i < fcinfo3.nargs; i++) { + if (fcinfo3.argnull[i] == true) { + shouldCallFunc = false; + break; + } + } + } + if (shouldCallFunc) { + result = fptr(&fcinfo3); + SET_EXPR_IS_NULL(fcinfo3.isnull); + } else { + SET_EXPR_IS_NULL(true); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +static Datum JitInvokePGFunctionNImpl(PGFunction fptr, int collationId, int isStrict, Datum* args, int* isnull, + Oid* argTypes, int argCount, Oid functionId) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile ListCell* cells = nullptr; + volatile Const* constArgs = nullptr; + volatile int spiConnectId = SPI_connectid(); + + FunctionCallInfoData fcinfo; + InitFunctionCallInfoData(fcinfo, NULL, argCount, (Oid)collationId, NULL, NULL); + PG_TRY(); + { + bool shouldCallFunc = true; + for (int i = 0; i < argCount; ++i) { + fcinfo.arg[i] = args[i]; + fcinfo.argnull[i] = isnull[i]; + if (isStrict && isnull[i]) { + SET_EXPR_IS_NULL(true); + shouldCallFunc = false; + break; + } + } + + if (shouldCallFunc) { + // some functions require types in flinfo.fn_expr, so we fake it as required + FmgrInfo flinfo = {}; + FuncExpr funcExpr = {}; + List funcArgs = {}; + cells = (ListCell*)MOT::MemSessionAlloc(sizeof(ListCell) * argCount); + constArgs = (Const*)MOT::MemSessionAlloc(sizeof(Const) * argCount); + if ((cells == nullptr) || (constArgs == nullptr)) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_OUT_OF_LOGICAL_MEMORY), + errmsg("MOT/JIT execution cannot call PG function"), + errdetail("Out of session memory"))); + } + PrepareFmgrInfo( + &fcinfo, &flinfo, &funcExpr, &funcArgs, (ListCell*)cells, (Const*)constArgs, argTypes, argCount); + flinfo.fn_oid = functionId; // for invoke unjittable using plpgsql_call_handler + + // call the function + result = fptr(&fcinfo); + SET_EXPR_IS_NULL(fcinfo.isnull); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + // cleanup + if (cells != nullptr) { + MOT::MemSessionFree((void*)cells); + } + if (args != nullptr) { + MOT::MemSessionFree((void*)constArgs); + } + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +Datum JitInvokePGFunctionN( + PGFunction fptr, int collationId, int isStrict, Datum* args, int* isnull, Oid* argTypes, int argCount) +{ + return JitInvokePGFunctionNImpl(fptr, collationId, isStrict, args, isnull, argTypes, argCount, 0); +} + +uint8_t* JitMemSessionAlloc(uint32_t size) +{ + return (uint8_t*)MOT::MemSessionAlloc(size); +} + +void JitMemSessionFree(uint8_t* ptr) +{ + MOT::MemSessionFree(ptr); +} + +MOT::Row* searchRow(MOT::Table* table, MOT::Key* key, int access_mode_value, int innerRow, int subQueryIndex) +{ + MOT_LOG_DEBUG("searchRow(): Searching row at table %p by key %p", table, key); MOT::Row* row = NULL; MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; MOT::AccessType access_mode = (MOT::AccessType)access_mode_value; - row = curr_txn->RowLookupByKey(table, access_mode, key); + MOT::RC rc = MOT::RC_OK; + + row = curr_txn->RowLookupByKey(table, access_mode, key, rc); if (row == nullptr) { - MOT_LOG_DEBUG("Row not found"); + MOT_LOG_DEBUG("searchRow(): Row not found"); + if (rc != MOT::RC_OK) { + u_sess->mot_cxt.jit_context->m_rc = rc; + return nullptr; + } + } else { + MOT_ASSERT(rc == MOT::RC_OK); + if (access_mode == MOT::AccessType::WR) { + rc = curr_txn->UpdateLastRowState(access_mode); + if (rc != MOT::RC_OK) { + u_sess->mot_cxt.jit_context->m_rc = rc; + row = nullptr; + } else { + row = curr_txn->GetLastAccessedDraft(); + } + } } - MOT_LOG_DEBUG("Returning row: %p", row); + MOT_LOG_DEBUG("searchRow(): Returning row: %p", row); return row; } @@ -564,15 +1052,17 @@ void resetBitmapSet(MOT::BitmapSet* bmp) int writeRow(MOT::Row* row, MOT::BitmapSet* bmp) { MOT::RC rc = MOT::RC_ERROR; - MOT_LOG_DEBUG("Writing row %p to DB", row); + MOT_LOG_DEBUG("writeRow(): Writing row %p to DB", row); MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; - MOT_LOG_DEBUG("Current txn is: %p", curr_txn); + MOT_LOG_DEBUG("writeRow(): Current txn is: %p", curr_txn); rc = curr_txn->UpdateLastRowState(MOT::AccessType::WR); - MOT_LOG_DEBUG("Row write result: %d", (int)rc); + MOT_LOG_DEBUG("writeRow(): Row write result: %d", (int)rc); if (rc == MOT::RC_OK) { - MOT_LOG_DEBUG("Overwriting row %p with bitset %p", row, bmp); + MOT_LOG_DEBUG("writeRow(): Overwriting row %p with bitset %p", row, bmp); rc = curr_txn->OverwriteRow(row, *bmp); - MOT_LOG_DEBUG("Overwrite row result: %d", (int)rc); + MOT_LOG_DEBUG("writeRow(): Overwrite row result: %d", (int)rc); + } else { + MOT_LOG_DEBUG("writeRow(): Failed to update row %p with rc (%s)", row, MOT::RcToString(rc)); } return (int)rc; } @@ -596,12 +1086,24 @@ MOT::Row* createNewRow(MOT::Table* table) int insertRow(MOT::Table* table, MOT::Row* row) { MOT::RC rc = MOT::RC_ERROR; - MOT_LOG_DEBUG("Inserting row %p to DB", row); + MOT_LOG_DEBUG("insertRow(): Inserting row %p to DB", row); MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; - MOT_LOG_DEBUG("Current txn is: %p", curr_txn); + MOT_LOG_DEBUG("insertRow(): Current txn is: %p", curr_txn); + uint8_t* bits = (uint8_t*)row->GetData(); + for (uint32_t i = 1; i < table->GetFieldCount(); i++) { + MOT::Column* col = table->GetField(i); + if (!col->GetIsDropped() && col->m_isNotNull && !BITMAP_GET(bits, (i - 1))) { + MOT_LOG_DEBUG("insertRow(): Cannot set null to not-nullable column %d (%s)", i, col->m_name); + if (u_sess->mot_cxt.jit_context != NULL) { + u_sess->mot_cxt.jit_context->m_execState->m_nullViolationTable = table; + u_sess->mot_cxt.jit_context->m_execState->m_nullColumnId = i; + } + return (int)MOT::RC_NULL_VIOLATION; + } + } rc = table->InsertRow(row, curr_txn); if (rc != MOT::RC_OK) { - MOT_LOG_DEBUG("Insert row failed with rc: %d", (int)rc); + MOT_LOG_DEBUG("insertRow(): Insert row failed with rc: %d", (int)rc); } return (int)rc; } @@ -609,12 +1111,12 @@ int insertRow(MOT::Table* table, MOT::Row* row) int deleteRow() { MOT::RC rc = MOT::RC_ERROR; - MOT_LOG_DEBUG("Deleting row from DB"); + MOT_LOG_DEBUG("deleteRow(): Deleting row from DB"); MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; - MOT_LOG_DEBUG("Current txn is: %p", curr_txn); + MOT_LOG_DEBUG("deleteRow(): Current txn is: %p", curr_txn); rc = curr_txn->DeleteLastRow(); if (rc != MOT::RC_OK) { - MOT_LOG_DEBUG("Delete row failed with rc: %d", (int)rc); + MOT_LOG_DEBUG("deleteRow(): Delete row failed with rc: %d", (int)rc); } return (int)rc; } @@ -634,10 +1136,12 @@ int setConstNullBit(MOT::Table* table, MOT::Row* row, int table_colid, int isnul { MOT_LOG_DEBUG("Setting const null bit at table %p row %p column id %d: isnull=%d", table, row, table_colid, isnull); - if (isnull && table->GetField(table_colid)->m_isNotNull) { - MOT_LOG_DEBUG("Cannot set null to not-nullable column %d", table_colid); + MOT::Column* column = table->GetField(table_colid); + if (isnull && column->m_isNotNull) { + MOT_LOG_DEBUG("Cannot set null to not-nullable column %d (%s)", table_colid, column->m_name); if (u_sess->mot_cxt.jit_context != NULL) { - u_sess->mot_cxt.jit_context->m_nullColumnId = table_colid; + u_sess->mot_cxt.jit_context->m_execState->m_nullViolationTable = table; + u_sess->mot_cxt.jit_context->m_execState->m_nullColumnId = table_colid; } return (int)MOT::RC_NULL_VIOLATION; } @@ -655,7 +1159,7 @@ int setConstNullBit(MOT::Table* table, MOT::Row* row, int table_colid, int isnul int setExprResultNullBit(MOT::Table* table, MOT::Row* row, int table_colid) { - int isnull = getExprArgIsNull(0); // final result is always put in arg zero + int isnull = GET_EXPR_IS_NULL(); MOT_LOG_DEBUG("Setting expression result isnull at column %d to: %d", table_colid, isnull); return setConstNullBit(table, row, table_colid, isnull); } @@ -672,6 +1176,29 @@ void execStoreVirtualTuple(TupleTableSlot* slot) ::ExecStoreVirtualTuple(slot); } +struct SelectRowFunctor { + MOT::Table* m_table; + MOT::Row* m_row; + TupleTableSlot* m_slot; + int m_tableColumnId; + int m_tupleColumnId; + + SelectRowFunctor(MOT::Table* table, MOT::Row* row, TupleTableSlot* slot, int tableColumnId, int tupleColumnId) + : m_table(table), m_row(row), m_slot(slot), m_tableColumnId(tableColumnId), m_tupleColumnId(tupleColumnId) + {} + + inline void operator()() + { + uint8_t* rowData = const_cast(m_row->GetData()); + m_slot->tts_tupleDescriptor->attrs[m_tupleColumnId]->attnum = m_tableColumnId; + MOTAdaptor::MOTToDatum(m_table, + m_slot->tts_tupleDescriptor->attrs[m_tupleColumnId], + rowData, + &(m_slot->tts_values[m_tupleColumnId]), + &(m_slot->tts_isnull[m_tupleColumnId])); + } +}; + void selectColumn(MOT::Table* table, MOT::Row* row, TupleTableSlot* slot, int table_colid, int tuple_colid) { MOT_LOG_DEBUG("Selecting into tuple column %d from table %s, row %p column id %d [%s]", @@ -687,7 +1214,7 @@ void selectColumn(MOT::Table* table, MOT::Row* row, TupleTableSlot* slot, int ta rowData, &(slot->tts_values[tuple_colid]), &(slot->tts_isnull[tuple_colid])); - DBG_PRINT_DATUM("Column Datum", + DEBUG_PRINT_DATUM("Column Datum", slot->tts_tupleDescriptor->attrs[tuple_colid]->atttypid, slot->tts_values[tuple_colid], slot->tts_isnull[tuple_colid]); @@ -695,7 +1222,7 @@ void selectColumn(MOT::Table* table, MOT::Row* row, TupleTableSlot* slot, int ta void setTpProcessed(uint64_t* tp_processed, uint64_t rows) { - MOT_LOG_DEBUG("Setting tp_processed at %p to %" PRIu64 "", tp_processed, rows); + MOT_LOG_DEBUG("Setting tp_processed at %p to %" PRIu64 " rows", tp_processed, rows); *tp_processed = rows; } @@ -727,7 +1254,8 @@ void FillKeyPattern(MOT::Key* key, unsigned char pattern, int offset, int size) offset, size, MOT::HexStr(key->GetKeyBuf(), key->GetKeyLength()).c_str()); - key->FillPattern(pattern, size, offset); + MOT_ASSERT((offset + size) <= key->GetKeyLength()); + (void)key->FillPattern(pattern, size, offset); MOT_LOG_DEBUG("Filled key %p pattern %u at offset %d, size %d: %s", key, (unsigned)pattern, @@ -756,19 +1284,28 @@ MOT::IndexIterator* searchIterator(MOT::Index* index, MOT::Key* key, int forward bool forwardDirection = forward_scan ? true : false; bool found = false; - MOT_LOG_DEBUG("Creating begin iterator for index %p from key %p (include_bound=%s): %s", + MOT_LOG_DEBUG("searchIterator(): Creating begin iterator for index %p from key %p (include_bound=%s): %s", index, key, include_bound ? "true" : "false", MOT::HexStr(key->GetKeyBuf(), key->GetKeyLength()).c_str()); MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; - MOT_LOG_DEBUG("searchIterator: Current txn is: %p", curr_txn); + MOT_LOG_DEBUG("searchIterator(): Current txn is: %p", curr_txn); itr = index->Search(key, matchKey, forwardDirection, curr_txn->GetThdId(), found); - if (!found) { - MOT_LOG_DEBUG("searchIterator: Exact match not found, still continuing, itr=%p", itr); + if (itr != nullptr) { + if (!found) { + MOT_LOG_DEBUG("searchIterator(): Exact match not found, still continuing, itr=%p", itr); + } else { + MOT_LOG_DEBUG("searchIterator(): Exact match found with iterator %p", itr); + } } else { - MOT_LOG_DEBUG("searchIterator: Exact match found with iterator %p", itr); + MOT::RC rootRc = MOT_GET_ROOT_ERROR_RC(); + u_sess->mot_cxt.jit_context->m_rc = rootRc != MOT::RC_OK ? rootRc : MOT::RC_MEMORY_ALLOCATION_ERROR; + MOT_LOG_ERROR("searchIterator(): Failed to create iterator for index (%s) with rc (%s) rootRc (%s)", + index->GetName().c_str(), + MOT::RcToString(u_sess->mot_cxt.jit_context->m_rc), + MOT::RcToString(rootRc)); } return itr; @@ -776,7 +1313,17 @@ MOT::IndexIterator* searchIterator(MOT::Index* index, MOT::Key* key, int forward MOT::IndexIterator* beginIterator(MOT::Index* index) { - return index->Begin(MOTCurrThreadId); + MOT::IndexIterator* itr = index->Begin(MOTCurrThreadId); + if (itr == nullptr) { + MOT::RC rootRc = MOT_GET_ROOT_ERROR_RC(); + u_sess->mot_cxt.jit_context->m_rc = rootRc != MOT::RC_OK ? rootRc : MOT::RC_MEMORY_ALLOCATION_ERROR; + MOT_LOG_ERROR("beginIterator(): Failed to create iterator for index (%s) with rc (%s) rootRc (%s)", + index->GetName().c_str(), + MOT::RcToString(u_sess->mot_cxt.jit_context->m_rc), + MOT::RcToString(rootRc)); + } + + return itr; } MOT::IndexIterator* createEndIterator(MOT::Index* index, MOT::Key* key, int forward_scan, int include_bound) @@ -787,8 +1334,8 @@ MOT::IndexIterator* createEndIterator(MOT::Index* index, MOT::Key* key, int forw forward_scan ? false : true; // in forward scan search key or previous, in backwards scan search key or next bool found = false; - MOT_LOG_DEBUG( - "Creating end iterator (forward_scan=%d, forwardDirection=%s, include_bound=%s)) for index %p from key %p: %s", + MOT_LOG_DEBUG("createEndIterator(): Creating end iterator (forward_scan=%d, forwardDirection=%s, " + "include_bound=%s)) for index %p from key %p: %s", forward_scan, forwardDirection ? "true" : "false", include_bound ? "true" : "false", @@ -799,79 +1346,146 @@ MOT::IndexIterator* createEndIterator(MOT::Index* index, MOT::Key* key, int forw MOT_LOG_DEBUG("Current txn is: %p", curr_txn); itr = index->Search(key, matchKey, forwardDirection, curr_txn->GetThdId(), found); - if (!found) { - MOT_LOG_DEBUG("createEndIterator: Exact match not found, still continuing, itr=%p", itr); + if (itr != nullptr) { + if (!found) { + MOT_LOG_DEBUG("createEndIterator(): Exact match not found, still continuing, itr=%p", itr); + } else { + MOT_LOG_DEBUG("createEndIterator(): Exact match found with iterator %p", itr); + } } else { - MOT_LOG_DEBUG("createEndIterator: Exact match found with iterator %p", itr); + MOT::RC rootRc = MOT_GET_ROOT_ERROR_RC(); + u_sess->mot_cxt.jit_context->m_rc = rootRc != MOT::RC_OK ? rootRc : MOT::RC_MEMORY_ALLOCATION_ERROR; + MOT_LOG_ERROR("createEndIterator(): Failed to create iterator for index (%s) with rc (%s) rootRc (%s)", + index->GetName().c_str(), + MOT::RcToString(u_sess->mot_cxt.jit_context->m_rc), + MOT::RcToString(rootRc)); } + return itr; } int isScanEnd(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int forward_scan) { - MOT_LOG_DEBUG("Checking if scan ended"); + MOT_LOG_DEBUG("isScanEnd(): Checking if scan ended"); + MOT_ASSERT(itr != nullptr); + if (!itr->IsValid()) { + MOT_LOG_DEBUG("isScanEnd(): begin iterator %p is not valid", itr); + return 1; + } + + // in case of full-scan end iterator is null, and then we can definitely conclude that scan has not ended yet + // (since begin iterator is still valid) + if (end_itr == nullptr) { +#ifdef MOT_JIT_FULL_SCAN + MOT_LOG_DEBUG("isScanEnd(): full-scan not ended yet"); + return 0; +#else + MOT_LOG_TRACE("isScanEnd(): end iterator is null. index %p", index); + u_sess->mot_cxt.jit_context->m_rc = MOT::RC_MEMORY_ALLOCATION_ERROR; + return 1; +#endif + } + + // check end iterator validity + if (!end_itr->IsValid()) { + MOT_LOG_DEBUG("isScanEnd(): end iterator %p is not valid", end_itr); + return 1; + } + + // compare begin/end iterator keys of iterated rows + const MOT::Key* startKey = reinterpret_cast(const_cast(itr->GetKey())); + const MOT::Key* endKey = reinterpret_cast(const_cast(end_itr->GetKey())); + if ((startKey == nullptr) || (endKey == nullptr)) { + MOT_LOG_DEBUG("isScanEnd(): either start key or end key is invalid"); + return 1; + } + MOT_LOG_DEBUG("isScanEnd(): Start key: %s", MOT::HexStr(startKey->GetKeyBuf(), startKey->GetKeyLength()).c_str()); + MOT_LOG_DEBUG("isScanEnd(): End key: %s", MOT::HexStr(endKey->GetKeyBuf(), endKey->GetKeyLength()).c_str()); int res = 0; - - if (itr != nullptr && !itr->IsValid()) { - MOT_LOG_DEBUG("isScanEnd(): begin iterator is not valid"); - res = 1; - } else if (end_itr != nullptr && !end_itr->IsValid()) { - MOT_LOG_DEBUG("isScanEnd(): end iterator is not valid"); - res = 1; + int cmpRes = memcmp(startKey->GetKeyBuf(), endKey->GetKeyBuf(), index->GetKeySizeNoSuffix()); + MOT_LOG_DEBUG("isScanEnd(): cmpRes = %d", cmpRes); + if (forward_scan) { + if (cmpRes > 0) { // end key is included in scan (so == is not reported as end of scan) + MOT_LOG_DEBUG("isScanEnd(): end of forward scan detected"); + res = 1; + } } else { - const MOT::Key* startKey = nullptr; - const MOT::Key* endKey = nullptr; - - if (itr != nullptr) { - startKey = reinterpret_cast(const_cast(itr->GetKey())); - MOT_LOG_DEBUG("Start key: %s", MOT::HexStr(startKey->GetKeyBuf(), startKey->GetKeyLength()).c_str()); - } - if (end_itr != nullptr) { - endKey = reinterpret_cast(const_cast(end_itr->GetKey())); - MOT_LOG_DEBUG("End key: %s", MOT::HexStr(endKey->GetKeyBuf(), endKey->GetKeyLength()).c_str()); - } - - if (startKey != nullptr && endKey != nullptr) { - int cmpRes = memcmp(startKey->GetKeyBuf(), endKey->GetKeyBuf(), index->GetKeySizeNoSuffix()); - MOT_LOG_DEBUG("isScanEnd(): cmpRes = %d", cmpRes); - if (forward_scan) { - if (cmpRes > 0) { // end key is included in scan (so == is not reported as end of scan) - MOT_LOG_DEBUG("isScanEnd(): end of forward scan detected"); - res = 1; - } - } else { - if (cmpRes < 0) { - MOT_LOG_DEBUG("isScanEnd(): end of backward scan detected"); - res = 1; - } - } - } else { - MOT_LOG_DEBUG("isScanEnd(): either start key or end key is invalid"); + if (cmpRes < 0) { + MOT_LOG_DEBUG("isScanEnd(): end of backward scan detected"); + res = 1; } } return res; } -MOT::Row* getRowFromIterator( - MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int access_mode, int forward_scan) +int CheckRowExistsInIterator(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* endItr, int forwardScan) +{ + bool rowExists = false; + MOT::RC rc = MOT::RC_OK; + + MOT_LOG_DEBUG("CheckRowExistsInIterator(): Retrieving row from iterator %p", itr); + MOT::TxnManager* currTxn = u_sess->mot_cxt.jit_txn; + do { + // get row from iterator using primary sentinel + MOT::Sentinel* sentinel = itr->GetPrimarySentinel(); + rowExists = currTxn->IsRowExist(sentinel, rc); + if (!rowExists) { + if (rc != MOT::RC_OK) { + u_sess->mot_cxt.jit_context->m_rc = rc; + itr->Invalidate(); + break; + } + MOT_LOG_DEBUG("CheckRowExistsInIterator(): Encountered non-existent row during scan, advancing iterator"); + itr->Next(); + continue; + } + + MOT_ASSERT(rc == MOT::RC_OK); + // verify the scan did not pass the end iterator + if (isScanEnd(index, itr, endItr, forwardScan)) { + MOT_LOG_DEBUG("CheckRowExistsInIterator(): Detected end of scan"); + rowExists = false; + itr->Invalidate(); + break; + } + + // prepare already for next round + itr->Next(); + break; + } while (itr->IsValid()); + + MOT_LOG_DEBUG( + "CheckRowExistsInIterator(): Retrieved row %s from iterator %p", rowExists ? "exists" : "not-exist", itr); + return rowExists ? 1 : 0; +} + +MOT::Row* getRowFromIterator(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int access_mode, + int forward_scan, int innerRow, int subQueryIndex) { MOT::Row* row = NULL; + MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; + MOT::RC rc = MOT::RC_OK; MOT_LOG_DEBUG("getRowFromIterator(): Retrieving row from iterator %p", itr); - MOT::TxnManager* curr_txn = u_sess->mot_cxt.jit_txn; do { // get row from iterator using primary sentinel MOT::Sentinel* sentinel = itr->GetPrimarySentinel(); row = curr_txn->RowLookup((MOT::AccessType)access_mode, sentinel, rc); if (row == NULL) { + if (rc != MOT::RC_OK) { + u_sess->mot_cxt.jit_context->m_rc = rc; + itr->Invalidate(); + break; + } MOT_LOG_DEBUG("getRowFromIterator(): Encountered NULL row during scan, advancing iterator"); itr->Next(); continue; } + MOT_ASSERT(rc == MOT::RC_OK); // verify the scan did not pass the end iterator if (isScanEnd(index, itr, end_itr, forward_scan)) { MOT_LOG_DEBUG("getRowFromIterator(): Detected end of scan"); @@ -885,6 +1499,18 @@ MOT::Row* getRowFromIterator( break; } while (itr->IsValid()); + if (row) { + if (access_mode == MOT::AccessType::WR) { + rc = curr_txn->UpdateLastRowState((MOT::AccessType)access_mode); + if (rc != MOT::RC_OK) { + u_sess->mot_cxt.jit_context->m_rc = rc; + row = nullptr; + itr->Invalidate(); + } else { + row = curr_txn->GetLastAccessedDraft(); + } + } + } MOT_LOG_DEBUG("getRowFromIterator(): Retrieved row %p from iterator %p", row, itr); return row; } @@ -892,9 +1518,11 @@ MOT::Row* getRowFromIterator( void destroyIterator(MOT::IndexIterator* itr) { MOT_LOG_DEBUG("Destroying iterator %p", itr); - itr->Invalidate(); - itr->Destroy(); - delete itr; + if (itr != nullptr) { + itr->Invalidate(); + itr->Destroy(); + delete itr; + } } /*--------------------------- Stateful Execution Helpers ---------------------------*/ @@ -902,17 +1530,18 @@ void setStateIterator(MOT::IndexIterator* itr, int begin_itr, int inner_scan) { MOT_LOG_DEBUG("Setting state iterator %p (begin_itr=%d, inner_scan=%d)", itr, begin_itr, inner_scan); if (u_sess->mot_cxt.jit_context) { + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { if (begin_itr) { - u_sess->mot_cxt.jit_context->m_innerBeginIterator = itr; + execState->m_innerBeginIterator = itr; } else { - u_sess->mot_cxt.jit_context->m_innerEndIterator = itr; + execState->m_innerEndIterator = itr; } } else { if (begin_itr) { - u_sess->mot_cxt.jit_context->m_beginIterator = itr; + execState->m_beginIterator = itr; } else { - u_sess->mot_cxt.jit_context->m_endIterator = itr; + execState->m_endIterator = itr; } } } @@ -922,17 +1551,18 @@ MOT::IndexIterator* getStateIterator(int begin_itr, int inner_scan) { MOT::IndexIterator* result = NULL; if (u_sess->mot_cxt.jit_context) { + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { if (begin_itr) { - result = u_sess->mot_cxt.jit_context->m_innerBeginIterator; + result = execState->m_innerBeginIterator; } else { - result = u_sess->mot_cxt.jit_context->m_innerEndIterator; + result = execState->m_innerEndIterator; } } else { if (begin_itr) { - result = u_sess->mot_cxt.jit_context->m_beginIterator; + result = execState->m_beginIterator; } else { - result = u_sess->mot_cxt.jit_context->m_endIterator; + result = execState->m_endIterator; } } } @@ -944,17 +1574,18 @@ int isStateIteratorNull(int begin_itr, int inner_scan) { int is_null = 0; if (u_sess->mot_cxt.jit_context) { + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { if (begin_itr) { - is_null = u_sess->mot_cxt.jit_context->m_innerBeginIterator ? 0 : 1; + is_null = execState->m_innerBeginIterator ? 0 : 1; } else { - is_null = u_sess->mot_cxt.jit_context->m_innerEndIterator ? 0 : 1; + is_null = execState->m_innerEndIterator ? 0 : 1; } } else { if (begin_itr) { - is_null = u_sess->mot_cxt.jit_context->m_beginIterator ? 0 : 1; + is_null = execState->m_beginIterator ? 0 : 1; } else { - is_null = u_sess->mot_cxt.jit_context->m_endIterator ? 0 : 1; + is_null = execState->m_endIterator ? 0 : 1; } } } @@ -966,16 +1597,13 @@ int isStateIteratorNull(int begin_itr, int inner_scan) int isStateScanEnd(int forward_scan, int inner_scan) { int result = -1; + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - result = isScanEnd(u_sess->mot_cxt.jit_context->m_innerIndex, - u_sess->mot_cxt.jit_context->m_innerBeginIterator, - u_sess->mot_cxt.jit_context->m_innerEndIterator, - forward_scan); + result = isScanEnd( + jitContext->m_innerIndex, execState->m_innerBeginIterator, execState->m_innerEndIterator, forward_scan); } else { - result = isScanEnd(u_sess->mot_cxt.jit_context->m_index, - u_sess->mot_cxt.jit_context->m_beginIterator, - u_sess->mot_cxt.jit_context->m_endIterator, - forward_scan); + result = isScanEnd(jitContext->m_index, execState->m_beginIterator, execState->m_endIterator, forward_scan); } MOT_LOG_DEBUG( "Checked if state scan ended (forward_scan=%d, inner_scan=%d): result=%d", forward_scan, inner_scan, result); @@ -985,18 +1613,24 @@ int isStateScanEnd(int forward_scan, int inner_scan) MOT::Row* getRowFromStateIterator(int access_mode, int forward_scan, int inner_scan) { MOT::Row* result = NULL; + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - result = getRowFromIterator(u_sess->mot_cxt.jit_context->m_innerIndex, - u_sess->mot_cxt.jit_context->m_innerBeginIterator, - u_sess->mot_cxt.jit_context->m_innerEndIterator, + result = getRowFromIterator(jitContext->m_innerIndex, + execState->m_innerBeginIterator, + execState->m_innerEndIterator, access_mode, - forward_scan); + forward_scan, + inner_scan, + -1); } else { - result = getRowFromIterator(u_sess->mot_cxt.jit_context->m_index, - u_sess->mot_cxt.jit_context->m_beginIterator, - u_sess->mot_cxt.jit_context->m_endIterator, + result = getRowFromIterator(jitContext->m_index, + execState->m_beginIterator, + execState->m_endIterator, access_mode, - forward_scan); + forward_scan, + inner_scan, + -1); } MOT_LOG_DEBUG("Retrieved row %p from state iterator (access_mode=%d, forward_scan=%d, inner_scan=%d): result=%d", result, @@ -1009,23 +1643,24 @@ MOT::Row* getRowFromStateIterator(int access_mode, int forward_scan, int inner_s void destroyStateIterators(int inner_scan) { MOT_LOG_DEBUG("Destroying state iterators (inner_scan=%d)", inner_scan); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - if (u_sess->mot_cxt.jit_context->m_innerBeginIterator) { - destroyIterator(u_sess->mot_cxt.jit_context->m_innerBeginIterator); - u_sess->mot_cxt.jit_context->m_innerBeginIterator = NULL; + if (execState->m_innerBeginIterator) { + destroyIterator(execState->m_innerBeginIterator); + execState->m_innerBeginIterator = NULL; } - if (u_sess->mot_cxt.jit_context->m_innerEndIterator) { - destroyIterator(u_sess->mot_cxt.jit_context->m_innerEndIterator); - u_sess->mot_cxt.jit_context->m_innerEndIterator = NULL; + if (execState->m_innerEndIterator) { + destroyIterator(execState->m_innerEndIterator); + execState->m_innerEndIterator = NULL; } } else { - if (u_sess->mot_cxt.jit_context->m_beginIterator) { - destroyIterator(u_sess->mot_cxt.jit_context->m_beginIterator); - u_sess->mot_cxt.jit_context->m_beginIterator = NULL; + if (execState->m_beginIterator) { + destroyIterator(execState->m_beginIterator); + execState->m_beginIterator = NULL; } - if (u_sess->mot_cxt.jit_context->m_endIterator) { - destroyIterator(u_sess->mot_cxt.jit_context->m_endIterator); - u_sess->mot_cxt.jit_context->m_endIterator = NULL; + if (execState->m_endIterator) { + destroyIterator(execState->m_endIterator); + execState->m_endIterator = NULL; } } } @@ -1033,20 +1668,22 @@ void destroyStateIterators(int inner_scan) void setStateScanEndFlag(int scan_ended, int inner_scan) { MOT_LOG_DEBUG("Setting state scan end flag to %d (inner_scan=%d)", scan_ended, inner_scan); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - u_sess->mot_cxt.jit_context->m_innerScanEnded = scan_ended; + execState->m_innerScanEnded = scan_ended; } else { - u_sess->mot_cxt.jit_context->m_scanEnded = scan_ended; + execState->m_scanEnded = scan_ended; } } int getStateScanEndFlag(int inner_scan) { int result = -1; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - result = (int)u_sess->mot_cxt.jit_context->m_innerScanEnded; + result = (int)execState->m_innerScanEnded; } else { - result = (int)u_sess->mot_cxt.jit_context->m_scanEnded; + result = (int)execState->m_scanEnded; } MOT_LOG_DEBUG("Retrieved state scan end flag %d (inner_scan=%d)", result, inner_scan); return result; @@ -1055,30 +1692,33 @@ int getStateScanEndFlag(int inner_scan) void resetStateRow(int inner_scan) { MOT_LOG_DEBUG("Resetting state row to NULL (inner_scan=%d)", inner_scan); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - u_sess->mot_cxt.jit_context->m_innerRow = NULL; + execState->m_innerRow = NULL; } else { - u_sess->mot_cxt.jit_context->m_row = NULL; + execState->m_row = NULL; } } void setStateRow(MOT::Row* row, int inner_scan) { MOT_LOG_DEBUG("Setting state row to %p (inner_scan=%d)", row, inner_scan); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - u_sess->mot_cxt.jit_context->m_innerRow = row; + execState->m_innerRow = row; } else { - u_sess->mot_cxt.jit_context->m_row = row; + execState->m_row = row; } } MOT::Row* getStateRow(int inner_scan) { MOT::Row* result = NULL; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - result = u_sess->mot_cxt.jit_context->m_innerRow; + result = execState->m_innerRow; } else { - result = u_sess->mot_cxt.jit_context->m_row; + result = execState->m_row; } MOT_LOG_DEBUG("Retrieved state row %p (inner_scan=%d)", result, inner_scan); return result; @@ -1086,25 +1726,27 @@ MOT::Row* getStateRow(int inner_scan) void copyOuterStateRow() { - MOT_LOG_DEBUG("Copying outer state row %p into safe copy %p (for JOIN query)", - u_sess->mot_cxt.jit_context->m_row, - u_sess->mot_cxt.jit_context->m_outerRowCopy); - u_sess->mot_cxt.jit_context->m_outerRowCopy->Copy(u_sess->mot_cxt.jit_context->m_row); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + MOT_LOG_DEBUG( + "Copying outer state row %p into safe copy %p (for JOIN query)", execState->m_row, execState->m_outerRowCopy); + execState->m_outerRowCopy->Copy(execState->m_row); } MOT::Row* getOuterStateRowCopy() { - MOT_LOG_DEBUG("Retrieved outer state row copy %p (for JOIN query)", u_sess->mot_cxt.jit_context->m_outerRowCopy); - return u_sess->mot_cxt.jit_context->m_outerRowCopy; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + MOT_LOG_DEBUG("Retrieved outer state row copy %p (for JOIN query)", execState->m_outerRowCopy); + return execState->m_outerRowCopy; } int isStateRowNull(int inner_scan) { int result = -1; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; if (inner_scan) { - result = (u_sess->mot_cxt.jit_context->m_innerRow == NULL) ? 1 : 0; + result = (execState->m_innerRow == NULL) ? 1 : 0; } else { - result = (u_sess->mot_cxt.jit_context->m_row == NULL) ? 1 : 0; + result = (execState->m_row == NULL) ? 1 : 0; } MOT_LOG_DEBUG("Checked if state row is null (inner_scan=%d): result=%d", inner_scan, result); return result; @@ -1112,190 +1754,307 @@ int isStateRowNull(int inner_scan) void resetStateLimitCounter() { + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; MOT_LOG_DEBUG("Resetting state limit counter to 0"); - u_sess->mot_cxt.jit_context->m_limitCounter = 0; + execState->m_limitCounter = 0; } void incrementStateLimitCounter() { - ++u_sess->mot_cxt.jit_context->m_limitCounter; - MOT_LOG_DEBUG("Incremented state limit counter to %d", u_sess->mot_cxt.jit_context->m_limitCounter); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + ++execState->m_limitCounter; + MOT_LOG_DEBUG("Incremented state limit counter to %u", execState->m_limitCounter); } int getStateLimitCounter() { - return u_sess->mot_cxt.jit_context->m_limitCounter; - MOT_LOG_DEBUG("Retrieved state limit counter with value %d", u_sess->mot_cxt.jit_context->m_limitCounter); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + MOT_LOG_DEBUG("Retrieved state limit counter with value %u", execState->m_limitCounter); + return execState->m_limitCounter; } -void prepareAvgArray(int element_type, int element_count) +void DebugPrintNumericArray(Datum arrayDatum) { + ArrayType* avgArray = (ArrayType*)DatumGetPointer(arrayDatum); + int elmlen = -1; + int elmbyval = false; + char elmalign = 'i'; + bool isNull; + int idx = 0; // 1-based index + Datum value = array_ref(avgArray, 1, &idx, 0, elmlen, elmbyval, elmalign, &isNull); + MOT_LOG_DEBUG("AVG Array[0] at %p", value); + DEBUG_PRINT_DATUM("AVG Array[0]", avgArray->elemtype, value, isNull); + idx = 1; + value = array_ref(avgArray, 1, &idx, 0, elmlen, elmbyval, elmalign, &isNull); + MOT_LOG_DEBUG("AVG Array[1] at %p", value); + DEBUG_PRINT_DATUM("AVG Array[1]", avgArray->elemtype, value, isNull); +} + +void prepareAvgArray(int aggIndex, int element_type, int element_count) +{ + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; MOT_LOG_DEBUG("Preparing AVG() array with %d elements of type %d", element_count, element_type); Datum* elements = (Datum*)palloc(sizeof(Datum) * element_count); for (int i = 0; i < element_count; ++i) { if (element_type == NUMERICOID) { elements[i] = makeNumericZero(); + DEBUG_PRINT_DATUM("AVG initial Element", NUMERICOID, elements[i], false); } else if (element_type == INT8OID) { elements[i] = Int64GetDatum(0); } else if (element_type == FLOAT8OID) { elements[i] = Float8GetDatum(0.0f); } + MOT_LOG_DEBUG("AVG Element %d: %p", i, elements[i]); } - int elmlen = 0; + // initialize values for int8 and float8 + bool elmbyval = true; // int8, sometimes also float8 (depending on compile flags) + int elmlen = 8; + char elmalign = 'd'; if (element_type == NUMERICOID) { elmlen = -1; // Numeric is a varlena object (see definition of NumericData at utils/numeric.h, and numeric type // in catalog/pg_type.h) - } else if (element_type == INT8OID || element_type == FLOAT8OID) { - elmlen = 8; + elmbyval = false; + elmalign = 'i'; + } else if (element_type == FLOAT8OID) { + elmbyval = FLOAT8PASSBYVAL; } - ArrayType* avg_array = construct_array(elements, element_count, element_type, elmlen, true, 0); - u_sess->mot_cxt.jit_context->m_avgArray = PointerGetDatum(avg_array); + ArrayType* avg_array = construct_array(elements, element_count, element_type, elmlen, elmbyval, elmalign); + execState->m_avgArray = PointerGetDatum(avg_array); + execState->m_aggValueIsNull = 1; +#ifdef MOT_JIT_DEBUG + MOT_LOG_DEBUG("AVG array at %p", execState->m_avgArray); + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + DebugPrintNumericArray(execState->m_avgArray); + } +#endif } -Datum loadAvgArray() +Datum loadAvgArray(int aggIndex) { - Datum result = u_sess->mot_cxt.jit_context->m_avgArray; - MOT_LOG_DEBUG("Loaded AVG() array %" PRIu64, (uint64_t)result); + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + Datum result = execState->m_avgArray; + MOT_LOG_DEBUG("Loaded AVG() array at %p", result); +#ifdef MOT_JIT_DEBUG + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + DebugPrintNumericArray(result); + } +#endif return result; } -void saveAvgArray(Datum avg_array) +void saveAvgArray(int aggIndex, Datum avg_array) { - MOT_LOG_DEBUG("Saving AVG() array %" PRIu64, (uint64_t)avg_array); - u_sess->mot_cxt.jit_context->m_avgArray = avg_array; + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + MOT_LOG_DEBUG("Saving AVG() array %p", avg_array); +#ifdef MOT_JIT_DEBUG + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + DebugPrintNumericArray(avg_array); + } +#endif + execState->m_avgArray = avg_array; + execState->m_aggValueIsNull = 0; } -Datum computeAvgFromArray(int element_type) +Datum ComputeAvg(PGFunction func, Datum avgArray, bool* isNull) { + FunctionCallInfoData fcinfo; + Datum result; + + InitFunctionCallInfoData(fcinfo, NULL, 1, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = avgArray; + fcinfo.argnull[0] = false; + + result = (*func)(&fcinfo); + + // communicate back is-null status + *isNull = fcinfo.isnull; + + return result; +} + +Datum computeAvgFromArray(int aggIndex, int element_type) +{ + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + + MOT_LOG_DEBUG("Computing AVG() array %" PRIu64, (uint64_t)execState->m_avgArray); +#ifdef MOT_JIT_DEBUG + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + DebugPrintNumericArray(execState->m_avgArray); + } +#endif + + // attention: in case of null result an ereport exception is thrown, so we call avg functions carefully + bool isNull = false; Datum avg = PointerGetDatum(NULL); if (element_type == NUMERICOID) { - avg = DirectFunctionCall1(numeric_avg, u_sess->mot_cxt.jit_context->m_avgArray); + avg = ComputeAvg(numeric_avg, execState->m_avgArray, &isNull); } else if (element_type == INT8OID) { - avg = DirectFunctionCall1(int8_avg, u_sess->mot_cxt.jit_context->m_avgArray); + avg = ComputeAvg(int8_avg, execState->m_avgArray, &isNull); } else if (element_type == FLOAT8OID) { - avg = DirectFunctionCall1(float8_avg, u_sess->mot_cxt.jit_context->m_avgArray); + avg = ComputeAvg(float8_avg, execState->m_avgArray, &isNull); } - MOT_LOG_DEBUG("Computed AVG() from array %" PRIu64 ": %f", - (uint64_t)u_sess->mot_cxt.jit_context->m_avgArray, - numericToDouble(avg)); + + SET_EXPR_IS_NULL(isNull); return avg; } -void resetAggValue(int element_type) +void resetAggValue(int aggIndex, int element_type) { + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; MOT_LOG_DEBUG("Resetting aggregated value to zero of type %d", element_type); if (element_type == NUMERICOID) { - u_sess->mot_cxt.jit_context->m_aggValue = makeNumericZero(); + execState->m_aggValue = makeNumericZero(); } else if (element_type == INT8OID) { - u_sess->mot_cxt.jit_context->m_aggValue = Int64GetDatum(0); + execState->m_aggValue = Int64GetDatum(0); } else if (element_type == FLOAT4OID) { - u_sess->mot_cxt.jit_context->m_aggValue = Float4GetDatum(((float)0.0)); + execState->m_aggValue = Float4GetDatum(((float)0.0)); } else if (element_type == FLOAT8OID) { - u_sess->mot_cxt.jit_context->m_aggValue = Float8GetDatum(((double)0.0)); + execState->m_aggValue = Float8GetDatum(((double)0.0)); } + execState->m_aggValueIsNull = 1; } -void resetCountAgg() +Datum getAggValue(int aggIndex) { - MOT_LOG_DEBUG("Resetting aggregated count value to 0"); - u_sess->mot_cxt.jit_context->m_aggValue = Int64GetDatum(0); -} - -Datum getAggValue() -{ - Datum result = u_sess->mot_cxt.jit_context->m_aggValue; + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + Datum result = execState->m_aggValue; MOT_LOG_DEBUG("Retrieved aggregated value: %" PRIu64, result); return result; } -void setAggValue(Datum value) +void setAggValue(int aggIndex, Datum value) { + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; MOT_LOG_DEBUG("Setting aggregated value to: %" PRIu64, value); - u_sess->mot_cxt.jit_context->m_aggValue = value; + execState->m_aggValue = value; + execState->m_aggValueIsNull = 0; } -void resetAggMaxMinNull() +int getAggValueIsNull(int aggIndex) { - MOT_LOG_DEBUG("Resetting aggregated max/min null flag to true"); - u_sess->mot_cxt.jit_context->m_maxMinAggNull = 1; -} - -void setAggMaxMinNotNull() -{ - MOT_LOG_DEBUG("Setting aggregated max/min null flag to false"); - u_sess->mot_cxt.jit_context->m_maxMinAggNull = 0; -} - -int getAggMaxMinIsNull() -{ - int result = (int)u_sess->mot_cxt.jit_context->m_maxMinAggNull; - MOT_LOG_DEBUG("Retrieved aggregated max/min null flag: %d", result); + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + int result = execState->m_aggValueIsNull; + MOT_LOG_DEBUG("Retrieved aggregated value is-null: %d", result); return result; } -void prepareDistinctSet(int element_type) +int setAggValueIsNull(int aggIndex, int isNull) { + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; + execState->m_aggValueIsNull = isNull; + MOT_LOG_DEBUG("Set aggregated value is-null to: %d", isNull); + return 0; +} + +void prepareDistinctSet(int aggIndex, int element_type) +{ + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; switch (element_type) { case INT8OID: case INT4OID: case INT2OID: case INT1OID: MOT_LOG_DEBUG("Preparing distinct integer set for type %d", element_type); - u_sess->mot_cxt.jit_context->m_distinctSet = prepareDistinctIntSet(); + execState->m_distinctSet = prepareDistinctIntSet(); break; case FLOAT4OID: case FLOAT8OID: - case NUMERICOID: MOT_LOG_DEBUG("Preparing distinct double-precision value set for type %d", element_type); - u_sess->mot_cxt.jit_context->m_distinctSet = prepareDistinctDoubleSet(); + execState->m_distinctSet = prepareDistinctDoubleSet(); break; + + case NUMERICOID: + MOT_LOG_DEBUG("Preparing distinct numeric value set for type %d", element_type); + execState->m_distinctSet = prepareDistinctNumericSet(); + break; + + case VARCHAROID: + MOT_LOG_DEBUG("Preparing distinct varchar value set for type %d", element_type); + execState->m_distinctSet = prepareDistinctVarcharSet(); + break; + default: MOT_LOG_ERROR("Input element type %d is invalid", element_type); break; } + + if (execState->m_distinctSet == nullptr) { + MOT_LOG_ERROR("Failed to create distinctSet for element type %d", element_type); + RaiseResourceLimitFault(); + } } -int insertDistinctItem(int element_type, Datum item) +int insertDistinctItem(int aggIndex, int element_type, Datum item) { + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; int result = 0; switch (element_type) { case INT8OID: MOT_LOG_DEBUG("Inserting distinct int8 value %" PRIu64, DatumGetInt64(item)); - result = insertDistinctIntItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetInt64(item)); + result = insertDistinctIntItem(execState->m_distinctSet, DatumGetInt64(item)); break; case INT4OID: MOT_LOG_DEBUG("Inserting distinct int4 value %" PRIu64, DatumGetInt32(item)); - result = insertDistinctIntItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetInt32(item)); + result = insertDistinctIntItem(execState->m_distinctSet, DatumGetInt32(item)); break; case INT2OID: MOT_LOG_DEBUG("Inserting distinct int2 value %" PRIu64, DatumGetInt16(item)); - result = insertDistinctIntItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetInt16(item)); + result = insertDistinctIntItem(execState->m_distinctSet, DatumGetInt16(item)); break; case INT1OID: MOT_LOG_DEBUG("Inserting distinct int1 value %" PRIu64, DatumGetInt8(item)); - result = insertDistinctIntItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetInt8(item)); + result = insertDistinctIntItem(execState->m_distinctSet, DatumGetInt8(item)); break; case FLOAT4OID: MOT_LOG_DEBUG("Inserting distinct float4 value %f", (float)DatumGetFloat4(item)); - result = insertDistinctDoubleItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetFloat4(item)); + result = insertDistinctDoubleItem(execState->m_distinctSet, DatumGetFloat4(item)); break; case FLOAT8OID: MOT_LOG_DEBUG("Inserting distinct float8 value %f", (double)DatumGetFloat8(item)); - result = insertDistinctDoubleItem(u_sess->mot_cxt.jit_context->m_distinctSet, DatumGetFloat8(item)); + result = insertDistinctDoubleItem(execState->m_distinctSet, DatumGetFloat8(item)); break; case NUMERICOID: - MOT_LOG_DEBUG("Inserting distinct numeric value %f", numericToDouble(item)); - result = insertDistinctDoubleItem(u_sess->mot_cxt.jit_context->m_distinctSet, numericToDouble(item)); + MOT_LOG_DEBUG("Inserting distinct numeric value %f", JitExec::NumericToDouble(item)); + result = insertDistinctNumericItem(execState->m_distinctSet, item); break; + + case VARCHAROID: + DEBUG_PRINT_DATUM("Inserting distinct varchar value", VARCHAROID, item, false); + result = insertDistinctVarcharItem(execState->m_distinctSet, item); + break; + default: MOT_LOG_ERROR("Input element type %d is invalid", element_type); break; @@ -1303,23 +2062,36 @@ int insertDistinctItem(int element_type, Datum item) return result; } -void destroyDistinctSet(int element_type) +void destroyDistinctSet(int aggIndex, int element_type) { + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + VERIFY_PTR(queryExecState->m_aggExecState, "NULL aggregate array"); + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[aggIndex]; switch (element_type) { case INT8OID: case INT4OID: case INT2OID: case INT1OID: MOT_LOG_DEBUG("Destroying distinct integer set for type %d", element_type); - destroyDistinctIntSet(u_sess->mot_cxt.jit_context->m_distinctSet); + destroyDistinctIntSet(execState->m_distinctSet); break; case FLOAT4OID: case FLOAT8OID: - case NUMERICOID: MOT_LOG_DEBUG("Destroying distinct double-precision value set for type %d", element_type); - destroyDistinctDoubleSet(u_sess->mot_cxt.jit_context->m_distinctSet); + destroyDistinctDoubleSet(execState->m_distinctSet); break; + + case NUMERICOID: + MOT_LOG_DEBUG("Destroying distinct numeric value set for type %d", element_type); + destroyDistinctNumericSet(execState->m_distinctSet); + break; + + case VARCHAROID: + MOT_LOG_DEBUG("Destroying distinct varchar value set for type %d", element_type); + destroyDistinctVarcharSet(execState->m_distinctSet); + break; + default: MOT_LOG_ERROR("Input element type %d is invalid", element_type); break; @@ -1332,27 +2104,27 @@ void resetTupleDatum(TupleTableSlot* slot, int tuple_colid, int zero_type) MOT_LOG_DEBUG("Resetting tuple %p column %d datum to typed zero by type %d", slot, tuple_colid, zero_type) switch (zero_type) { case NUMERICOID: - writeTupleDatum(slot, tuple_colid, makeNumericZero()); + writeTupleDatum(slot, tuple_colid, makeNumericZero(), 1); break; case FLOAT8OID: - writeTupleDatum(slot, tuple_colid, Float8GetDatum(0)); + writeTupleDatum(slot, tuple_colid, Float8GetDatum(0), 1); break; case FLOAT4OID: - writeTupleDatum(slot, tuple_colid, Float4GetDatum(0)); + writeTupleDatum(slot, tuple_colid, Float4GetDatum(0), 1); break; case INT8OID: - writeTupleDatum(slot, tuple_colid, Int64GetDatum(0)); + writeTupleDatum(slot, tuple_colid, Int64GetDatum(0), 1); break; case INT4OID: - writeTupleDatum(slot, tuple_colid, Int32GetDatum(0)); + writeTupleDatum(slot, tuple_colid, Int32GetDatum(0), 1); break; case INT2OID: - writeTupleDatum(slot, tuple_colid, Int16GetDatum(0)); + writeTupleDatum(slot, tuple_colid, Int16GetDatum(0), 1); break; default: @@ -1361,26 +2133,25 @@ void resetTupleDatum(TupleTableSlot* slot, int tuple_colid, int zero_type) } } -Datum readTupleDatum(TupleTableSlot* slot, int tuple_colid, int arg_pos) +Datum readTupleDatum(TupleTableSlot* slot, int tuple_colid) { MOT_LOG_DEBUG("Reading datum from tuple column %d ", tuple_colid); Datum result = slot->tts_values[tuple_colid]; - DBG_PRINT_DATUM("Pre-sum Tuple Datum", + DEBUG_PRINT_DATUM("Pre-sum Tuple Datum", slot->tts_tupleDescriptor->attrs[tuple_colid]->atttypid, slot->tts_values[tuple_colid], slot->tts_isnull[tuple_colid]); bool isnull = (result == PointerGetDatum(NULL)); - setExprArgIsNull(arg_pos, (int)isnull); + SET_EXPR_IS_NULL((int)isnull); return result; } -void writeTupleDatum(TupleTableSlot* slot, int tuple_colid, Datum datum) +void writeTupleDatum(TupleTableSlot* slot, int tuple_colid, Datum datum, int isnull) { - MOT_LOG_DEBUG("Writing datum to tuple column %d ", tuple_colid); - bool isnull = (datum == PointerGetDatum(NULL)); + MOT_LOG_DEBUG("Writing datum to tuple column %d, isnull %d", tuple_colid, isnull); slot->tts_values[tuple_colid] = datum; slot->tts_isnull[tuple_colid] = isnull; - DBG_PRINT_DATUM("Post-sum Tuple Datum", + DEBUG_PRINT_DATUM("Post-sum Tuple Datum", slot->tts_tupleDescriptor->attrs[tuple_colid]->atttypid, slot->tts_values[tuple_colid], slot->tts_isnull[tuple_colid]); @@ -1388,39 +2159,2098 @@ void writeTupleDatum(TupleTableSlot* slot, int tuple_colid, Datum datum) Datum SelectSubQueryResult(int subQueryIndex) { - JitExec::JitContext* jitContext = u_sess->mot_cxt.jit_context; - return readTupleDatum(jitContext->m_subQueryData[subQueryIndex].m_slot, 0, 0); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + return readTupleDatum(execState->m_subQueryExecState[subQueryIndex].m_slot, 0); } void CopyAggregateToSubQueryResult(int subQueryIndex) { + // sub-query can have only 1 aggregate MOT_LOG_DEBUG("Copying aggregate datum to sub-query %d slot", subQueryIndex); - JitExec::JitContext* jitContext = u_sess->mot_cxt.jit_context; - writeTupleDatum(jitContext->m_subQueryData[subQueryIndex].m_slot, 0, jitContext->m_aggValue); + JitExec::JitQueryExecState* queryExecState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + JitExec::JitAggExecState* execState = &queryExecState->m_aggExecState[0]; + writeTupleDatum(queryExecState->m_subQueryExecState[subQueryIndex].m_slot, + 0, + execState->m_aggValue, + execState->m_aggValueIsNull); } TupleTableSlot* GetSubQuerySlot(int subQueryIndex) { - return u_sess->mot_cxt.jit_context->m_subQueryData[subQueryIndex].m_slot; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + return execState->m_subQueryExecState[subQueryIndex].m_slot; } MOT::Table* GetSubQueryTable(int subQueryIndex) { - return u_sess->mot_cxt.jit_context->m_subQueryData[subQueryIndex].m_table; + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + return jitContext->m_subQueryContext[subQueryIndex].m_table; } MOT::Index* GetSubQueryIndex(int subQueryIndex) { - return u_sess->mot_cxt.jit_context->m_subQueryData[subQueryIndex].m_index; + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + return jitContext->m_subQueryContext[subQueryIndex].m_index; } MOT::Key* GetSubQuerySearchKey(int subQueryIndex) { - return u_sess->mot_cxt.jit_context->m_subQueryData[subQueryIndex].m_searchKey; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + return execState->m_subQueryExecState[subQueryIndex].m_searchKey; } MOT::Key* GetSubQueryEndIteratorKey(int subQueryIndex) { - return u_sess->mot_cxt.jit_context->m_subQueryData[subQueryIndex].m_endIteratorKey; + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)u_sess->mot_cxt.jit_context->m_execState; + return execState->m_subQueryExecState[subQueryIndex].m_endIteratorKey; +} + +int8_t* LlvmPushExceptionFrame() +{ + // allocate exception frame + size_t allocSize = sizeof(JitExec::JitFunctionExecState::ExceptionFrame); + JitExec::JitFunctionExecState::ExceptionFrame* exceptionFrame = + (JitExec::JitFunctionExecState::ExceptionFrame*)MOT::MemSessionAlloc(allocSize); + if (exceptionFrame == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Execute JIT LLVM Stored Procedure", + "Failed to allocate %u bytes for exception frame, while executing %s. Aborting execution.", + (unsigned)allocSize, + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_RESOURCE_LIMIT); + return nullptr; // to avoid compiler warning + } + + // set exception frame attributes and push exception frame + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + uint64_t frameIndex = (execState->m_exceptionStack != nullptr) ? execState->m_exceptionStack->m_frameIndex + 1 : 0; + exceptionFrame->m_frameIndex = frameIndex; + exceptionFrame->m_next = execState->m_exceptionStack; + execState->m_exceptionStack = exceptionFrame; + int8_t* frame = (int8_t*)exceptionFrame->m_jmpBuf; + MOT_LOG_DEBUG("Pushed exception frame #%d %p with jump buf at %p on exec state %p", + (int)frameIndex, + exceptionFrame, + frame, + execState); + return frame; +} + +int8_t* LlvmGetCurrentExceptionFrame() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + JitExec::JitFunctionExecState::ExceptionFrame* exceptionFrame = execState->m_exceptionStack; + if (exceptionFrame == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT LLVM Stored Procedure", + "Cannot get current exception frame: exception stack is empty, while executing %s. Aborting execution.", + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } + + MOT_LOG_DEBUG("Getting current exception frame #%d %p with jump buf at %p", + (int)exceptionFrame->m_frameIndex, + exceptionFrame, + (int8_t*)execState->m_exceptionStack->m_jmpBuf); + return (int8_t*)execState->m_exceptionStack->m_jmpBuf; +} + +int LlvmPopExceptionFrame() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + JitExec::JitFunctionExecState::ExceptionFrame* exceptionFrame = execState->m_exceptionStack; + if (exceptionFrame == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT LLVM Stored Procedure", + "Cannot pop exception frame: exception stack is empty, while executing %s. Aborting execution.", + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } + + MOT_LOG_DEBUG("Popping exception frame #%d %p with jump buf at %p from exec state %p", + (int)exceptionFrame->m_frameIndex, + exceptionFrame, + (int8_t*)exceptionFrame->m_jmpBuf, + execState); + execState->m_exceptionStack = exceptionFrame->m_next; + MOT::MemSessionFree(exceptionFrame); + return execState->m_exceptionValue; +} + +void LlvmThrowException(int exceptionValue) +{ + // verify there is a handler + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + if (jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + if (execState->m_exceptionStack == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT LLVM Stored Procedure", + "Cannot throw exception: exception stack is empty, while executing %s. Aborting execution.", + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } + + // set exception value and raise exception status + execState->m_exceptionStatus = 1; + execState->m_exceptionValue = exceptionValue; + + // jump to handler + MOT_LOG_DEBUG("Throwing exception, jumping to frame #%d %p with jump buf at %p on exec state %p", + (int)execState->m_exceptionStack->m_frameIndex, + execState->m_exceptionStack, + (int8_t*)execState->m_exceptionStack->m_jmpBuf, + execState); + siglongjmp(execState->m_exceptionStack->m_jmpBuf, execState->m_exceptionValue); + } else { + JitExec::JitExecState* execState = (JitExec::JitExecState*)jitContext->m_execState; + // set exception value and raise exception status + execState->m_exceptionStatus = 1; + execState->m_exceptionValue = exceptionValue; + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } +} + +void LlvmRethrowException() +{ + // verify there is a handler + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + if (execState->m_exceptionStack == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT LLVM Stored Procedure", + "Cannot re-throw exception: exception stack is empty, while executing %s. Aborting execution.", + u_sess->mot_cxt.jit_context->m_queryString); + RaiseFault(LLVM_FAULT_UNHANDLED_EXCEPTION); + } + + execState->m_exceptionStatus = 1; // raise again exception status flag + MOT_LOG_DEBUG("Re-throwing exception, jumping to frame #%d %p with jump buf at %p on exec state %p", + (int)execState->m_exceptionStack->m_frameIndex, + execState->m_exceptionStack, + (int8_t*)execState->m_exceptionStack->m_jmpBuf, + execState); + siglongjmp(execState->m_exceptionStack->m_jmpBuf, execState->m_exceptionValue); +} + +int LlvmGetExceptionValue() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + return execState->m_exceptionValue; +} + +int LlvmGetExceptionStatus() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + return execState->m_exceptionStatus; +} + +void LlvmResetExceptionStatus() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + execState->m_exceptionStatus = 0; +} + +void LlvmResetExceptionValue() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + execState->m_exceptionValue = 0; +} + +void LlvmUnwindExceptionFrame() +{ + // same as re-throw + MOT_LOG_DEBUG("Unwinding exception") + LlvmRethrowException(); +} + +void LlvmClearExceptionStack() +{ + MOT_LOG_DEBUG("Clearing exception stack"); + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + while (execState->m_exceptionStack != nullptr) { + JitExec::JitFunctionExecState::ExceptionFrame* exceptionFrame = execState->m_exceptionStack; + MOT_LOG_DEBUG("Popping exception frame #%d %p with jump buf at %p from exec state %p", + (int)exceptionFrame->m_frameIndex, + exceptionFrame, + (int8_t*)exceptionFrame->m_jmpBuf, + execState); + execState->m_exceptionStack = exceptionFrame->m_next; + MOT::MemSessionFree(exceptionFrame); + } +} + +void LLvmPrintFrame(const char* msg, int8_t* frame) +{ + MOT_LOG_DEBUG("%s: %p", msg, frame); +} + +void ValidateParams(ParamListInfo params, int paramId = -1) +{ + if (params == nullptr) { + MOT_LOG_ERROR("Attempt to access null parameter list"); + RaiseAccessViolationFault(); + } + + // if we reached here then parameters must be not null + MOT_ASSERT(params != nullptr); + if (paramId != -1) { + if (paramId >= params->numParams) { + MOT_LOG_ERROR("Attempt to access parameter (%d items) list at %p out of range: %d", + params->numParams, + params, + paramId); + RaiseAccessViolationFault(); + } + } +} + +void JitAbortFunction(int errorCode) +{ + RaiseFault(errorCode); +} + +int JitHasNullParam(ParamListInfo params) +{ + ValidateParams(params); // if null long jump is made + MOT_ASSERT(params != nullptr); + for (int i = 0; i < params->numParams; ++i) { + if (params->params[i].isnull) { + return 1; + } + } + return 0; +} + +int JitGetParamCount(ParamListInfo params) +{ + ValidateParams(params); // if null long jump is made + MOT_ASSERT(params != nullptr); + return params->numParams; +} + +Datum JitGetParamAt(ParamListInfo params, int paramId) +{ + ValidateParams(params, paramId); // if null long jump is made + MOT_ASSERT(params != nullptr); + return params->params[paramId].value; +} + +int JitIsParamNull(ParamListInfo params, int paramId) +{ + ValidateParams(params, paramId); // if null long jump is made + int result = (params->params[paramId].isnull ? 1 : 0); + MOT_LOG_DEBUG("JitIsParamNull(%d) = %s", paramId, result ? "true" : "false"); + return result; +} + +static ParamListInfo GetParamListForParam(ParamListInfo params, int& paramId) +{ + // attention: the parameter list passed into SP has the same size as parameter list passed to the query that + // invoked the SP, so parameter ids are the same + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + if (jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitExec::JitFunctionExecState* execState = (JitExec::JitFunctionExecState*)jitContext->m_execState; + JitExec::JitQueryContext* invokingContext = (JitExec::JitQueryContext*)execState->m_invokingContext; + if (invokingContext->m_invokeParamInfo[paramId].m_mode == JitExec::JitParamMode::JIT_PARAM_DIRECT) { + JitExec::JitQueryExecState* invokingExecState = + (JitExec::JitQueryExecState*)execState->m_invokingContext->m_execState; + params = invokingExecState->m_directParams; + paramId = invokingContext->m_invokeParamInfo[paramId].m_index; + MOT_LOG_DEBUG( + "Using parameters at %p from invoking context for parameter at modified pos %d", params, paramId); + return (ParamListInfo)params; + } + } + MOT_LOG_DEBUG("Using original parameters at %p for parameter %d", params, paramId); + return params; +} + +Datum* JitGetParamAtRef(ParamListInfo params, int paramId) +{ + // we check at the current context whether this parameter is direct passed-as-is or not + params = GetParamListForParam(params, paramId); + ValidateParams(params, paramId); // if null long jump is made + MOT_ASSERT(params != nullptr); + Datum* result = &(params->params[paramId].value); + MOT_LOG_DEBUG("Retrieved param %d ref %p from params %p", paramId, result, params); + DEBUG_PRINT_DATUM("datum value: ", params->params[paramId].ptype, *result, params->params[paramId].isnull); + return result; +} + +bool* JitIsParamNullRef(ParamListInfo params, int paramId) +{ + // we check at the current context with this parameter is direct passed-as-is or not + params = GetParamListForParam(params, paramId); + ValidateParams(params, paramId); // if null long jump is made + MOT_ASSERT(params != nullptr); + bool* result = ¶ms->params[paramId].isnull; + MOT_LOG_DEBUG("Retrieved param %d is-null ref %p from params %p: %d", paramId, result, params, (int)*result); + return result; +} + +ParamListInfo GetInvokeParamListInfo() +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_QUERY); + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext->m_commandType == JitExec::JIT_COMMAND_INVOKE); + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)jitContext->m_execState; + MOT_LOG_DEBUG("Invoke params are at %p", execState->m_invokeParams); + return execState->m_invokeParams; +} + +void DestroyParamListInfo(ParamListInfo params) +{ + MOT::MemSessionFree(params); +} + +void SetParamValue(ParamListInfo params, int paramId, int paramType, Datum datum, int isNull) +{ + params->params[paramId].value = datum; + params->params[paramId].ptype = paramType; + params->params[paramId].isnull = isNull ? true : false; + DEBUG_PRINT_DATUM("param: ", paramType, datum, params->params[paramId].isnull); +} + +void SetSPSubQueryParamValue(int subQueryId, int id, int type, Datum value, int isNull) +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionContext* jitContext = (JitExec::JitFunctionContext*)u_sess->mot_cxt.jit_context; + JitExec::JitFunctionExecState* functionExecState = (JitExec::JitFunctionExecState*)jitContext->m_execState; + + ParamExternData* param = &functionExecState->m_invokedQueryExecState[subQueryId].m_params->params[id]; + param->ptype = type; + param->value = value; + param->isnull = isNull ? true : false; + MOT_LOG_DEBUG("Passed to SP sub-query %d param: ", subQueryId); + DEBUG_PRINT_DATUM("Passed to SP sub-query param", type, value, isNull); +} + +inline void PullUpErrorInfo(JitExec::JitExecState* source, JitExec::JitExecState* target) +{ + target->m_nullColumnId = source->m_nullColumnId; + target->m_nullViolationTable = source->m_nullViolationTable; + target->m_errorMessage = source->m_errorMessage; + target->m_errorDetail = source->m_errorDetail; + target->m_errorHint = source->m_errorHint; + target->m_sqlState = source->m_sqlState; + target->m_sqlStateString = source->m_sqlStateString; +} + +static int InvokeJittedStoredProcedure(JitExec::JitQueryContext* jitContext) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile int res = 0; + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)jitContext->m_execState; + MOT_LOG_DEBUG("Invoking jittable SP %s with %u invoke parameters at %p", + jitContext->m_invokeContext->m_queryString, + jitContext->m_invokeParamCount, + execState->m_invokeParams); + + MOT_ASSERT(jitContext->m_invokeContext->m_execState != nullptr); + jitContext->m_invokeContext->m_execState->m_invokingContext = jitContext; + jitContext->m_execState->m_sqlState = 0; + jitContext->m_invokeContext->m_execState->m_sqlState = 0; + PG_TRY(); + { + res = JitExec::JitExecFunction(jitContext->m_invokeContext, + execState->m_invokeParams, + execState->m_invokeSlot, + execState->m_invokeTuplesProcessed, + execState->m_invokeScanEnded); + MOT_LOG_DEBUG("Finished executing invoked stored procedure %s with result: %d (%s)", + jitContext->m_invokeContext->m_queryString, + res, + MOT::RcToString((MOT::RC)res)); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + res = HandlePGError(jitContext->m_invokeContext->m_execState, "executing jitted SP", spiConnectId); + signalException = true; + } + PG_END_TRY(); + + // pull-up error info + PullUpErrorInfo(jitContext->m_invokeContext->m_execState, jitContext->m_execState); + + // signal to jitted function that an exception is to be thrown + if (signalException) { + SignalException(jitContext); + } + + return res; +} + +static bool CopyHeapTupleHeader(HeapTupleHeader tupleHeader, TupleTableSlot* slot) +{ + // we fake a tuple for things to work + HeapTupleData tupleData; + HeapTuple tuple = &tupleData; + tuple->t_data = tupleHeader; + + TupleDesc tupDesc = slot->tts_tupleDescriptor; + int tupDescAttrCount = tupDesc->natts; + int tupleAttrCount = PointerIsValid(tupleHeader) ? HeapTupleHeaderGetNatts(tupleHeader, tupDesc) : 0; + int resultIndex = 0; + for (int i = 0; i < tupDescAttrCount; ++i) { + // skip dropped columns in destination + if (tupDesc->attrs[i]->attisdropped) { + continue; + } + + bool isNull = true; + Datum value = (Datum)0; + if ((i < tupleAttrCount) && !tupDesc->attrs[i]->attisdropped) { + value = SPI_getbinval(tuple, tupDesc, i + 1, &isNull); + if (SPI_result != 0) { + MOT_LOG_TRACE( + "Failed to get datum value from tuple: %s (%u)", SPI_result_code_string(SPI_result), SPI_result); + return false; + } + } else { + continue; // skip dropped columns in source + } + SetSlotValue(slot, resultIndex++, value, isNull); + } + + return true; +} + +static int InvokeUnjittableStoredProcedure(JitExec::JitQueryContext* jitContext) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile int res = 0; + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile Datum* args = nullptr; + volatile int* isnull = nullptr; + volatile Oid* argTypes = nullptr; + volatile int spiConnectId = SPI_connectid(); + + JitExec::JitQueryExecState* execState = (JitExec::JitQueryExecState*)jitContext->m_execState; + MOT_LOG_DEBUG("Invoking unjittable SP %s with %u invoke parameters at %p", + jitContext->m_invokedQueryString, + jitContext->m_invokeParamCount, + execState->m_invokeParams); + + execState->m_sqlState = 0; + PG_TRY(); + { + // prepare parameters + int argCount = (int)jitContext->m_invokeParamCount; + args = (Datum*)palloc(argCount * sizeof(Datum)); + isnull = (int*)palloc(argCount * sizeof(int)); + argTypes = (Oid*)palloc(argCount * sizeof(Oid)); + for (int i = 0; i < argCount; ++i) { + if (jitContext->m_invokeParamInfo[i].m_mode == JitExec::JitParamMode::JIT_PARAM_DIRECT) { + int paramId = jitContext->m_invokeParamInfo[i].m_index; + MOT_LOG_DEBUG("Using direct parameter %d at modified pos %d", i, paramId); + args[i] = execState->m_directParams->params[paramId].value; + isnull[i] = execState->m_directParams->params[paramId].isnull; + argTypes[i] = execState->m_directParams->params[paramId].ptype; + } else { + MOT_LOG_DEBUG("Using passed parameter %d", i); + args[i] = execState->m_invokeParams->params[i].value; + isnull[i] = execState->m_invokeParams->params[i].isnull; + argTypes[i] = execState->m_invokeParams->params[i].ptype; + } + } + + // call plpgsql_call_handler + Datum resDatum = JitInvokePGFunctionNImpl(plpgsql_call_handler, + DEFAULT_COLLATION_OID, + 0, + (Datum*)args, + (int*)isnull, + (Oid*)argTypes, + argCount, + jitContext->m_invokedFunctionOid); + + // now copy datum to result tuple + if (execState->m_invokeSlot->tts_tupleDescriptor->natts > 1) { + // result datum is a tuple already in the caller's memory context, just copy the datum + HeapTupleHeader tupleHeader = (HeapTupleHeader)DatumGetPointer(resDatum); + if (!CopyHeapTupleHeader(tupleHeader, execState->m_invokeSlot)) { + MOT_LOG_TRACE("Failed to copy result slot"); + res = (int)MOT::RC_ERROR; + } + } else if (execState->m_invokeSlot->tts_tupleDescriptor->natts == 1) { + // it is OK to have a boxed tuple here as a single attribute + execState->m_invokeSlot->tts_values[0] = resDatum; + execState->m_invokeSlot->tts_isnull[0] = false; + (void)ExecStoreVirtualTuple(execState->m_invokeSlot); + } + MOT_LOG_DEBUG("Finished executing unjittable invoked stored procedure %s with result: %d (%s)", + jitContext->m_invokedQueryString, + res, + MOT::RcToString((MOT::RC)res)); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + res = MOT::RC_ERROR; + (void)MemoryContextSwitchTo(origCxt); + res = HandlePGError(jitContext->m_execState, "executing unjittable SP", spiConnectId); + signalException = true; + } + PG_END_TRY(); + + // note: no need to pull-up error info, error info already produced in caller's execution state + if (args != nullptr) { + pfree((void*)args); + } + if (isnull != nullptr) { + pfree((void*)isnull); + } + if (argTypes != nullptr) { + pfree((void*)argTypes); + } + // signal to jitted function that an exception is to be thrown + if (signalException) { + SignalException(jitContext); + } + + return res; +} + +int InvokeStoredProcedure() +{ + JitExec::JitQueryContext* jitContext = (JitExec::JitQueryContext*)u_sess->mot_cxt.jit_context; + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_QUERY); + MOT_ASSERT(jitContext->m_commandType == JitExec::JIT_COMMAND_INVOKE); + + int result = 0; + if (jitContext->m_invokeContext != nullptr) { + result = InvokeJittedStoredProcedure(jitContext); + } else { + result = InvokeUnjittableStoredProcedure(jitContext); + } + return result; +} + +int IsCompositeResult(TupleTableSlot* slot) +{ + int res = 0; + if ((slot->tts_tupleDescriptor->natts == 1) && slot->tts_tupleDescriptor->attrs[0]->atttypid == RECORDOID) { + res = 1; + } + MOT_LOG_DEBUG("Result is composite: %d", res); + return res; +} + +Datum* CreateResultDatums() +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionContext* jitContext = (JitExec::JitFunctionContext*)u_sess->mot_cxt.jit_context; + int natts = jitContext->m_rowTupDesc->natts; + Datum* dvalues = (Datum*)palloc0(natts * sizeof(Datum)); + MOT_LOG_DEBUG("Created %d datums for heap tuple at %p", natts, dvalues); + return dvalues; +} + +bool* CreateResultNulls() +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionContext* jitContext = (JitExec::JitFunctionContext*)u_sess->mot_cxt.jit_context; + int natts = jitContext->m_rowTupDesc->natts; + bool* nulls = (bool*)palloc(natts * sizeof(bool)); + MOT_LOG_DEBUG("Created %d nulls for heap tuple at %p", natts, nulls); + return nulls; +} + +void SetResultValue(Datum* datums, bool* nulls, int index, Datum value, int isNull) +{ + MOT_LOG_DEBUG("Setting heap tuple datum/null pair at index %d", index); + datums[index] = value; + nulls[index] = isNull ? true : false; +} + +Datum CreateResultHeapTuple(Datum* dvalues, bool* nulls) +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionContext* jitContext = (JitExec::JitFunctionContext*)u_sess->mot_cxt.jit_context; + + // we do not initialize anything, caller is responsible for that + HeapTuple tuple = heap_form_tuple(jitContext->m_rowTupDesc, dvalues, nulls); + MOT_LOG_DEBUG( + "Created heap tuple %p from datum/null arrays using tuple descriptor %p", tuple, jitContext->m_rowTupDesc); + + pfree_ext(dvalues); + pfree_ext(nulls); + + TupleConversionMap* tupMap = convert_tuples_by_position(jitContext->m_rowTupDesc, + jitContext->m_resultTupDesc, + gettext_noop("returned record type does not match expected record type"), + jitContext->m_functionOid); + + if (tupMap != nullptr) { + tuple = do_convert_tuple(tuple, tupMap); + free_conversion_map(tupMap); + MOT_LOG_DEBUG("Converted heap tuple at %p", tuple); + } + + return PointerGetDatum(tuple); +} + +void SetSlotValue(TupleTableSlot* slot, int tupleColId, Datum value, int isNull) +{ + MOT_LOG_DEBUG("Storing value in tuple column %d", tupleColId); + slot->tts_values[tupleColId] = value; + slot->tts_isnull[tupleColId] = isNull ? true : false; + DEBUG_PRINT_DATUM("Slot Datum", + slot->tts_tupleDescriptor->attrs[tupleColId]->atttypid, + slot->tts_values[tupleColId], + slot->tts_isnull[tupleColId]); +} + +Datum GetSlotValue(TupleTableSlot* slot, int tupleColId) +{ + MOT_LOG_DEBUG("Retrieving datum value in tuple %p column %d", slot, tupleColId); + Datum result = slot->tts_values[tupleColId]; + DEBUG_PRINT_DATUM("Slot Datum", + slot->tts_tupleDescriptor->attrs[tupleColId]->atttypid, + slot->tts_values[tupleColId], + slot->tts_isnull[tupleColId]); + return result; +} + +int GetSlotIsNull(TupleTableSlot* slot, int tupleColId) +{ + MOT_LOG_DEBUG("Retrieving datum is-null in tuple column %d", tupleColId); + int result = slot->tts_isnull[tupleColId] == true ? 1 : 0; + MOT_LOG_DEBUG("Slot Datum is-null: %d", result); + return result; +} + +static void ReleaseOneSubTransaction(bool rollback, uint32_t openSubTxCount) +{ + SubTransactionId subXid = InvalidSubTransactionId; + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + subXid = ::GetCurrentSubTransactionId(); + } + if (rollback) { + MOT_LOG_DEBUG("Rolling back current sub-transaction #%u: id %" PRIu64, openSubTxCount, (uint64_t)subXid); + ::RollbackAndReleaseCurrentSubTransaction(); + } else { + MOT_LOG_DEBUG("Committing current sub-transaction #%u: id %" PRIu64, openSubTxCount, (uint64_t)subXid); + // if current txn is already in aborted unrecoverable state then report error + if (u_sess->mot_cxt.jit_txn->IsTxnAborted()) { + raiseAbortTxnError(); + } else { + ::ReleaseCurrentSubTransaction(); + } + } +} + +SubTransactionId JitGetCurrentSubTransactionId() +{ + return ::GetCurrentSubTransactionId(); +} + +void JitReleaseAllSubTransactions(bool rollback, SubTransactionId untilSubXid) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + + if (rollback) { + MOT_LOG_DEBUG("Rolling-back %u open sub-transactions", execState->m_openSubTxCount); + } else { + MOT_LOG_DEBUG("Committing %u open sub-transactions", execState->m_openSubTxCount); + } + + PG_TRY(); + { + SubTransactionId subXid = ::GetCurrentSubTransactionId(); + while ((execState->m_openSubTxCount > 0) && (subXid != untilSubXid)) { + ReleaseOneSubTransaction(rollback, execState->m_openSubTxCount); + --execState->m_openSubTxCount; + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + const char* action = rollback ? "rolling back all sub-transactions" : "committing all sub-transactions"; + (void)MemoryContextSwitchTo(origCxt); + (void)HandlePGError(jitContext->m_execState, action, spiConnectId); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + if (jitContext->m_execState->m_sqlState == 0) { + // note: we could use a more informative error code + jitContext->m_execState->m_sqlState = ERRCODE_FDW_ERROR; + } + SignalException((JitExec::MotJitContext*)jitContext); + } + execState->m_openSubTxCount = 0; // must be set to zero even if encountered error +} + +void JitBeginBlockWithExceptions() +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + + if (execState->m_spiBlockId >= MOT_JIT_MAX_BLOCK_DEPTH) { + MOT_LOG_ERROR("Cannot push SPI block, reached maximum allowed %d", (int)MOT_JIT_MAX_BLOCK_DEPTH); + RaiseResourceLimitFault(); + } + + MOT_LOG_DEBUG("Entering statement block with exceptions, with SPI nesting %d", execState->m_spiBlockId); + PG_TRY(); + { + if (u_sess->mot_cxt.jit_txn->IsTxnAborted()) { + raiseAbortTxnError(); + } + int connectid = SPI_connectid(); + execState->m_spiConnectId[execState->m_spiBlockId] = connectid; + execState->m_savedContexts[execState->m_spiBlockId] = CurrentMemoryContext; + MOT_LOG_DEBUG("SPI connect id: depth=%d, saved=%d, cxt=[%s @%p]", + execState->m_spiBlockId, + connectid, + CurrentMemoryContext->name, + CurrentMemoryContext); + ::BeginInternalSubTransaction(NULL); + SubTransactionId subXid = ::GetCurrentSubTransactionId(); + MOT_LOG_DEBUG("Started sub-transaction #%u: id %" PRId64, execState->m_openSubTxCount, subXid); + ++execState->m_openSubTxCount; + execState->m_subTxns[execState->m_spiBlockId] = subXid; + ++execState->m_spiBlockId; + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + (void)HandlePGError(jitContext->m_execState, "SPI_connectid()"); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + if (jitContext->m_execState->m_sqlState == 0) { + // note: we could use a more informative error code + jitContext->m_execState->m_sqlState = ERRCODE_FDW_ERROR; + } + SignalException((JitExec::MotJitContext*)jitContext); + } +} + +void JitEndBlockWithExceptions() +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + + MOT_LOG_DEBUG("Exiting statement block with exceptions"); + if (execState->m_spiBlockId == 0) { + MOT_LOG_ERROR("Cannot end block with exceptions: no block is active"); + RaiseAccessViolationFault(); + } + + PG_TRY(); + { + --execState->m_spiBlockId; + SubTransactionId subXid = ::GetCurrentSubTransactionId(); + if (subXid == execState->m_subTxns[execState->m_spiBlockId]) { + MOT_LOG_DEBUG("Committing current sub-transaction #%" PRIu64 ": id %" PRIu64, + execState->m_openSubTxCount, + (uint64_t)subXid); + // if current txn is already in aborted unrecoverable state then report error + if (u_sess->mot_cxt.jit_txn->IsTxnAborted()) { + raiseAbortTxnError(); + } else { + ::ReleaseCurrentSubTransaction(); + --execState->m_openSubTxCount; + } + } + int savedConnectId = execState->m_spiConnectId[execState->m_spiBlockId]; + MOT_LOG_DEBUG("SPI connect id before calling restore: depth=%d, saved=%d, actual=%d", + execState->m_spiBlockId, + savedConnectId, + SPI_connectid()); + SPI_restore_connection(); + MOT_LOG_DEBUG("SPI connect id after calling restore: %d", SPI_connectid()); + execState->m_spiConnectId[execState->m_spiBlockId] = -1; + MemoryContext savedCxt = execState->m_savedContexts[execState->m_spiBlockId]; + MOT_ASSERT(savedCxt != nullptr); + if (savedCxt == nullptr) { + MOT_LOG_ERROR("Cannot end block with exceptions: saved memory context is null"); + RaiseAccessViolationFault(); + } + (void)MemoryContextSwitchTo(savedCxt); + MOT_LOG_DEBUG("Restored saved memory context: %s @%p", savedCxt->name, savedCxt); + execState->m_savedContexts[execState->m_spiBlockId] = nullptr; + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + (void)HandlePGError(jitContext->m_execState, "SPI_restore_connection()"); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + if (jitContext->m_execState->m_sqlState == 0) { + // note: we could use a more informative error code + jitContext->m_execState->m_sqlState = ERRCODE_FDW_ERROR; + } + SignalException((JitExec::MotJitContext*)jitContext); + } +} + +void JitCleanupBlockAfterException() +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + + MOT_LOG_DEBUG("Cleaning up statement block with exception"); + if (execState->m_spiBlockId == 0) { + MOT_LOG_ERROR("Cannot cleanup block with exceptions: no block is active"); + RaiseAccessViolationFault(); + } + + PG_TRY(); + { + --execState->m_spiBlockId; + SubTransactionId subXid = ::GetCurrentSubTransactionId(); + if (subXid == execState->m_subTxns[execState->m_spiBlockId]) { + MOT_LOG_DEBUG("Rolling back current sub-transaction #%" PRIu64 ": id %" PRIu64, + execState->m_openSubTxCount, + (uint64_t)subXid); + ::RollbackAndReleaseCurrentSubTransaction(); + --execState->m_openSubTxCount; + } + int savedConnectId = execState->m_spiConnectId[execState->m_spiBlockId]; + MOT_LOG_DEBUG("SPI connect id before calling disconnect/restore: depth=%d, saved=%d, actual=%d", + execState->m_spiBlockId, + savedConnectId, + SPI_connectid()); + SPI_disconnect(savedConnectId + 1); + MOT_LOG_DEBUG("SPI connect id after calling disconnect: %d", SPI_connectid()); + SPI_restore_connection(); + MOT_LOG_DEBUG("SPI connect id after calling restore: %d", SPI_connectid()); + execState->m_spiConnectId[execState->m_spiBlockId] = -1; + MemoryContext savedCxt = execState->m_savedContexts[execState->m_spiBlockId]; + MOT_ASSERT(savedCxt != nullptr); + if (savedCxt == nullptr) { + MOT_LOG_ERROR("Cannot cleanup block with exceptions: saved memory context is null"); + RaiseAccessViolationFault(); + } + (void)MemoryContextSwitchTo(savedCxt); + MOT_LOG_DEBUG("Restored saved memory context: %s @%p", savedCxt->name, savedCxt); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + (void)HandlePGError(jitContext->m_execState, "SPI_restore_connection()"); + signalException = true; + } + PG_END_TRY(); + + if (signalException) { + if (jitContext->m_execState->m_sqlState == 0) { + // note: we could use a more informative error code + jitContext->m_execState->m_sqlState = ERRCODE_FDW_ERROR; + } + SignalException((JitExec::MotJitContext*)jitContext); + } +} + +void JitSetExceptionOrigin(int origin) +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + execState->m_exceptionOrigin = origin; +} + +int JitGetExceptionOrigin() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + return execState->m_exceptionOrigin; +} + +int* JitGetExceptionOriginRef() +{ + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + return &execState->m_exceptionOrigin; +} + +void JitCleanupBeforeReturn(SubTransactionId initSubTxnId) +{ + // close all open blocks (commit sub-txns and release SPI resources) + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + while (execState->m_spiBlockId > 0) { + JitEndBlockWithExceptions(); + if (LlvmGetExceptionStatus() != 0) { + MOT_LOG_DEBUG("JitCleanupBeforeReturn(): Exception thrown, aborting"); + return; + } + } + + // roll back all open sub-transactions (there shouldn't be by now - this is a safety check) + SubTransactionId subTxnId = ::GetCurrentSubTransactionId(); + JitReleaseAllSubTransactions(true, subTxnId); +} + +static JitExec::JitFunctionExecState* GetFunctionExecStateVerify(int subQueryId) +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + JitExec::JitFunctionExecState* execState = GetFunctionExecState(); + JitExec::JitFunctionContext* functionContext = (JitExec::JitFunctionContext*)jitContext; + if (subQueryId >= (int)functionContext->m_SPSubQueryCount) { + MOT_REPORT_ERROR(MOT_ERROR_INDEX_OUT_OF_RANGE, + "Execute JIT Function", + "Attempt to access out-of-range sub-query %d denied: Current function has only %d sub-queries", + subQueryId, + (int)functionContext->m_SPSubQueryCount); + RaiseAccessViolationFault(); + } + return execState; +} + +static void PrepareInvokeParams(JitExec::JitFunctionContext* functionContext, + JitExec::JitFunctionExecState* functionExecState, JitExec::JitInvokedQueryExecState* invokeExecState, + int subQueryId) +{ + // pass parameters coming from stored procedure parameters, some of which may be directly passed from the query + // that invoked the stored procedure + JitExec::JitQueryContext* invokingContext = (JitExec::JitQueryContext*)functionExecState->m_invokingContext; + JitExec::JitQueryExecState* invokingExecState = (JitExec::JitQueryExecState*)invokingContext->m_execState; + JitExec::JitParamInfo* paramInfo = invokingContext->m_invokeParamInfo; + JitExec::JitCallSite* callSite = &functionContext->m_SPSubQueryList[subQueryId]; + MOT_LOG_DEBUG("Using direct params at %p", invokingExecState->m_directParams); + + // note: stored procedure arguments are NOT necessarily starting from index zero + for (int i = 0; i < callSite->m_callParamCount; ++i) { + JitExec::JitCallParamInfo* callParamInfo = &callSite->m_callParamInfo[i]; + int paramId = callParamInfo->m_invokeArgIndex; + // local variables were already passed by jitted SP, we need to pass only SP parameters into called query + if (callParamInfo->m_paramKind == JitExec::JitCallParamKind::JIT_CALL_PARAM_ARG) { + // get SP parameter index from datum index + MOT_ASSERT(paramId < (int)invokingContext->m_invokeParamCount); + if (paramId >= (int)invokingContext->m_invokeParamCount) { + MOT_REPORT_ERROR(MOT_ERROR_INDEX_OUT_OF_RANGE, + "JIT Execute Stored Procedure", + "Invalid parameter index %d, when invoking sub-query %d (%s) of stored procedure %s", + paramId, + subQueryId, + callSite->m_queryContext->m_queryString, + functionContext->m_queryString); + RaiseAccessViolationFault(); + } + if (paramInfo[paramId].m_mode == JitExec::JitParamMode::JIT_PARAM_DIRECT) { + // pass parameter directly from the query that invoked this SP into the called sub-query + // get invoke query parameter index from SP parameter index + int pos = paramInfo[paramId].m_index; + MOT_LOG_DEBUG("Passing direct parameter %d at caller pos %d", i, pos); + invokeExecState->m_params->params[paramId].value = invokingExecState->m_directParams->params[pos].value; + invokeExecState->m_params->params[paramId].isnull = + invokingExecState->m_directParams->params[pos].isnull; + } else { + // pass parameter from the SP itself (since it was modified from INVOKE query into SP) + MOT_LOG_DEBUG("Passing parameter copy %d", i); + invokeExecState->m_params->params[paramId].value = + functionExecState->m_functionParams->params[paramId].value; + invokeExecState->m_params->params[paramId].isnull = + functionExecState->m_functionParams->params[paramId].isnull; + } + } else { + MOT_LOG_DEBUG("Passing local variable %d (already set)", i); + } + if (!invokeExecState->m_params->params[paramId].isnull) { + DEBUG_PRINT_DATUM("Passing parameter to sub-query", + invokeExecState->m_params->params[paramId].ptype, + invokeExecState->m_params->params[paramId].value, + invokeExecState->m_params->params[paramId].isnull); + } + } +} + +static void VerifySpiResult(int rc, JitExec::JitCallSite* callSite) +{ + switch (rc) { + case SPI_OK_SELECT: + case SPI_OK_SELINTO: + case SPI_OK_UTILITY: + case SPI_OK_REWRITTEN: + AssertEreport(!callSite->m_isModStmt, MOD_MOT, "It should not be mod stmt."); + break; + + case SPI_OK_INSERT: + case SPI_OK_UPDATE: + case SPI_OK_DELETE: + case SPI_OK_INSERT_RETURNING: + case SPI_OK_UPDATE_RETURNING: + case SPI_OK_DELETE_RETURNING: + case SPI_OK_MERGE: + AssertEreport(callSite->m_isModStmt, MOD_MOT, "mod stmt is required."); + break; + + case SPI_ERROR_COPY: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("cannot COPY to/from client in PL/pgSQL"))); + break; + + case SPI_ERROR_TRANSACTION: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("only support commit/rollback transaction statements."), + errhint("Use a BEGIN block with an EXCEPTION clause instead of begin/end transaction."))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmodule(MOD_MOT), + errmsg("SPI_execute_plan_with_paramlist failed executing query \"%s\": %s", + callSite->m_queryString, + SPI_result_code_string(rc)))); + break; + } + + if (callSite->m_isInto) { + if (SPI_tuptable == nullptr) { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmodule(MOD_MOT), + errmsg("INTO used with a command that cannot return data"))); + } + } else { + if (SPI_tuptable != nullptr) { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmodule(MOD_MOT), + errmsg("query has no destination for result data"), + (rc == SPI_OK_SELECT) + ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") + : 0)); + } + } +} + +// based on convert_value_to_string() from pl_exec.cpp +static char* ConvertValueToString(Datum value, Oid valtype) +{ + char* result = nullptr; + Oid typoutput; + bool typIsVarlena = false; + + getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); + result = OidOutputFunctionCall(typoutput, value); + + return result; +} + +static Datum CastValue(PLpgSQL_type* typeDesc, Datum value, Oid valueType, bool isNull) +{ + Oid funcId = InvalidOid; + CoercionPathType coercionPath = COERCION_PATH_NONE; + Oid requiredType = typeDesc->typoid; + FmgrInfo* requiredInput = &typeDesc->typinput; + Oid requiredTypeIoParam = typeDesc->typioparam; + int32 requiredTypemod = typeDesc->atttypmod; + + if (valueType != requiredType || requiredTypemod != -1) { + if (!isNull) { + if (valueType == UNKNOWNOID) { + valueType = TEXTOID; + value = DirectFunctionCall1(textin, value); + } + + // get the implicit cast function from valueType to requiredType + coercionPath = find_coercion_pathway(requiredType, valueType, COERCION_ASSIGNMENT, &funcId); + if (funcId != InvalidOid && + !(coercionPath == COERCION_PATH_COERCEVIAIO || coercionPath == COERCION_PATH_ARRAYCOERCE)) { + value = OidFunctionCall1(funcId, value); + value = pl_coerce_type_typmod(value, requiredType, requiredTypemod); + } else { + char* extVal = ConvertValueToString(value, valueType); + value = InputFunctionCall(requiredInput, extVal, requiredTypeIoParam, requiredTypemod); + pfree_ext(extVal); + } + } else { + value = InputFunctionCall(requiredInput, NULL, requiredTypeIoParam, requiredTypemod); + } + } + + return value; +} + +static bool CopyHeapTuple( + JitExec::JitInvokedQueryExecState* invokeExecState, HeapTuple tuple, TupleDesc tupleDesc, bool switchContext = true) +{ + // switch to memory context of caller + MemoryContext oldCtx = switchContext ? JitExec::SwitchToSPICallerContext() : nullptr; + + // code adapted from exec_move_row() in pl_exec.cpp + int tupleDescAttrCount = tupleDesc ? tupleDesc->natts : 0; + int tupleAttrCount = HeapTupleIsValid(tuple) ? HeapTupleHeaderGetNatts(tuple->t_data, tupleDesc) : 0; + int resultIndex = 0; + for (int i = 0; i < tupleDescAttrCount; ++i) { + // skip dropped columns in destination + if (tupleDesc->attrs[i]->attisdropped) { + continue; + } + + bool isNull = true; + Datum value = (Datum)0; + if ((i < tupleAttrCount) && !tupleDesc->attrs[i]->attisdropped) { + value = SPI_getbinval(tuple, tupleDesc, i + 1, &isNull); + if (SPI_result != 0) { + MOT_LOG_TRACE( + "Failed to get datum value from tuple: %s (%u)", SPI_result_code_string(SPI_result), SPI_result); + return false; + } + } else { + continue; // skip dropped columns in source + } + Oid type = SPI_gettypeid(tupleDesc, i + 1); + + // perform proper type conversion as in exec_assign_value() at pl_exec.cpp + PLpgSQL_type* resultType = &invokeExecState->m_resultTypes[resultIndex]; + Datum resValue = CastValue(resultType, value, type, isNull); + resValue = JitExec::CopyDatum(resValue, resultType->typoid, isNull); + SetSlotValue(invokeExecState->m_resultSlot, resultIndex++, resValue, isNull); + } + + // restore current memory context + if (oldCtx) { + MOT_LOG_TRACE("Switching back to memory context %s @%p", oldCtx->name, oldCtx); + (void)MemoryContextSwitchTo(oldCtx); + } + return true; +} + +static void CopyTupleTableSlot(JitExec::JitInvokedQueryExecState* invokeExecState, TupleTableSlot* tuple) +{ + // we might get a minimal tuple from sort result + if (tuple->tts_mintuple != nullptr) { + HeapTuple htup = heap_tuple_from_minimal_tuple(tuple->tts_mintuple); + if (!HeapTupleIsValid(htup)) { + ereport(ERROR, (errmodule(MOD_MOT), errcode(ERRCODE_PLPGSQL_ERROR), errmsg("Failed to form heap tuple"))); + } + if (!CopyHeapTuple(invokeExecState, htup, tuple->tts_tupleDescriptor, false)) { + ereport(ERROR, (errmodule(MOD_MOT), errcode(ERRCODE_PLPGSQL_ERROR), errmsg("Failed to copy heap tuple"))); + } + return; + } + + // switch to memory context of caller (except for when processing minimal tuple of sort query result) + MemoryContext oldCtx = JitExec::SwitchToSPICallerContext(); + + // copy slot values to memory context of caller + TupleDesc tupDesc = tuple->tts_tupleDescriptor; + int resultIndex = 0; + for (int i = 0; i < tupDesc->natts; ++i) { + // skip dropped columns in destination + if (tupDesc->attrs[i]->attisdropped) { + continue; + } + + bool isNull = tuple->tts_isnull[i]; + Datum value = tuple->tts_values[i]; + Oid type = tuple->tts_tupleDescriptor->attrs[i]->atttypid; + + // perform proper type conversion as in exec_assign_value() at pl_exec.cpp + PLpgSQL_type* resultType = &invokeExecState->m_resultTypes[resultIndex]; + Datum resValue = CastValue(resultType, value, type, isNull); + resValue = JitExec::CopyDatum(resValue, resultType->typoid, isNull); + SetSlotValue(invokeExecState->m_resultSlot, resultIndex++, resValue, isNull); + } + + // restore current memory context + if (oldCtx) { + MOT_LOG_TRACE("Switching back to memory context %s @%p", oldCtx->name, oldCtx); + (void)MemoryContextSwitchTo(oldCtx); + } +} + +inline MOT::RC HandleSubQueryError(JitExec::JitExecState* execState, bool isJittable, int spiConnectId) +{ + const char* operation = isJittable ? "executing jitted SP sub-query" : "executing non-jitted SP sub-query"; + return HandlePGError(execState, operation, spiConnectId); +} + +static bool RecompilePlanIfNeeded( + JitExec::JitFunctionContext* jitContext, int subQueryId, JitExec::JitInvokedQueryExecState* invokeExecState) +{ + if ((invokeExecState->m_plan == nullptr) || needRecompilePlan(invokeExecState->m_plan)) { + MOT_LOG_TRACE("SPI plan needs recompilation"); + // clean up old plan + if (invokeExecState->m_plan != nullptr) { + MOT_LOG_TRACE("Destroying old plan"); + (void)SPI_freeplan(invokeExecState->m_plan); + invokeExecState->m_plan = nullptr; + } + + // regenerate new plan + MOT_LOG_TRACE("Regenerating new plan"); + invokeExecState->m_plan = JitExec::PrepareSpiPlan(jitContext, subQueryId, &invokeExecState->m_expr); + if (invokeExecState->m_plan == nullptr) { + MOT_LOG_TRACE("Failed to prepare SPI plan for non-jittable sub-query %u", subQueryId); + return false; + } + + // do what PG does... + if (ENABLE_CN_GPC && g_instance.plan_cache->CheckRecreateSPICachePlan(invokeExecState->m_plan)) { + g_instance.plan_cache->RecreateSPICachePlan(invokeExecState->m_plan); + } + } + + // setup parameter list for correct parsing (in case we hit parsing in RevalidateCachedQuery) + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + invokeExecState->m_params->parserSetupArg = (void*)invokeExecState->m_expr; + functionExecState->m_estate.cur_expr = invokeExecState->m_expr; // required by plpgsql_param_fetch + if (invokeExecState->m_expr != nullptr) { + invokeExecState->m_expr->func = functionExecState->m_function; + invokeExecState->m_expr->func->cur_estate = &functionExecState->m_estate; + } + + return true; +} + +static void PrintResultSlot(TupleTableSlot* resultSlot) +{ + if (resultSlot->tts_isempty) { + MOT_LOG_DEBUG("Query result is empty"); + } else { + MOT_LOG_BEGIN(MOT::LogLevel::LL_DEBUG, "Query result slot:"); + for (int i = 0; i < resultSlot->tts_tupleDescriptor->natts; ++i) { + MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "Datum[%d]: ", i); + Oid type = resultSlot->tts_tupleDescriptor->attrs[i]->atttypid; + JitExec::PrintDatum(MOT::LogLevel::LL_DEBUG, type, resultSlot->tts_values[i], resultSlot->tts_isnull[i]); + MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "\n"); + } + MOT_LOG_END(MOT::LogLevel::LL_DEBUG); + } +} + +static inline const char* GetDatumCString(Datum value) +{ + bytea* txt = DatumGetByteaP(value); + return VARDATA(txt); +} + +static inline void ReportErrorFromExecState(JitExec::JitExecState* execState) +{ + void* detailDatum = DatumGetPointer(execState->m_errorDetail); + void* hintDatum = DatumGetPointer(execState->m_errorHint); + if ((detailDatum != nullptr) && (hintDatum != nullptr)) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(execState->m_sqlState), + errmsg("%s", GetDatumCString(execState->m_errorMessage)), + errdetail("%s", GetDatumCString(execState->m_errorDetail)), + errhint("%s", GetDatumCString(execState->m_errorHint)))); + } else if (detailDatum != nullptr) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(execState->m_sqlState), + errmsg("%s", GetDatumCString(execState->m_errorMessage)), + errdetail("%s", GetDatumCString(execState->m_errorDetail)))); + } else if (hintDatum != nullptr) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(execState->m_sqlState), + errmsg("%s", GetDatumCString(execState->m_errorMessage)), + errhint("%s", GetDatumCString(execState->m_errorHint)))); + } else { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(execState->m_sqlState), + errmsg("%s", GetDatumCString(execState->m_errorMessage)))); + } +} + +static int ExecNonJitSubQueryPlan( + JitExec::JitCallSite* callSite, JitExec::JitInvokedQueryExecState* invokeExecState, int tcount, uint32_t subQueryId) +{ + // NOTE: we need to check here for a special use case: this is a jittable query that is executed once as non- + // jittable, and during this execution it was revalidated and executed as jittable through Executor path. + // In this case we need to check whether an error occurred, and if so report it properly + + // in order to make sure order of events described below is correct, we need to make sure error state is reset + if (callSite->m_queryContext != nullptr) { + JitExec::JitExecState* execState = callSite->m_queryContext->m_execState; + if (execState != nullptr) { + ResetErrorState(execState); + } + } + + // execute the query as non-jittable through SPI + // as explained above, it might be revalidated and transform into a jittable query, and get executed as jittable + // through the Executor path + int rc = SPI_execute_plan_with_paramlist(invokeExecState->m_plan, invokeExecState->m_params, false, tcount); + + // first check if this was a non-jittable execution of an invalid jittable query + if (callSite->m_queryContext != nullptr) { + // we avoid checking the validity of the query (so we have indication the jittable execution took place), since + // the valid bit might be overwritten (context invalidated) after jitted execution. + // instead, we just check the SQL state in the execution state - if it is not zero then definitely a jitted + // execution took place and it failed (the use case of non-failed jitted execution is of no interest here) + JitExec::JitExecState* execState = callSite->m_queryContext->m_execState; + if ((execState != nullptr) && (execState->m_sqlState != 0)) { // the execution ended with error + // query was executed as jittable and an error occurred, so we throw the same error again + ReportErrorFromExecState(execState); + } + } + + // next check return code from SPI execution + invokeExecState->m_spiResult = rc; + VerifySpiResult(rc, callSite); + rc = 0; // we are good, report success for now + + // now handle result tuple if needed + invokeExecState->m_tuplesProcessed = SPI_processed; + MOT_LOG_TRACE("Sub-query %u returned %u tuples processed: %s", subQueryId, SPI_processed, callSite->m_queryString); + if ((callSite->m_queryCmdType == CMD_SELECT) && (SPI_processed > 0)) { + SPITupleTable* tupTab = SPI_tuptable; + if (!CopyHeapTuple(invokeExecState, tupTab->vals[0], tupTab->tupdesc)) { + rc = SPI_ERROR_OPUNKNOWN; + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_PLPGSQL_ERROR), + errmsg("Failed to copy result tuple to caller's context for non-jittable sub-query %u: %s", + subQueryId, + callSite->m_queryString))); + } + if (MOT_CHECK_DEBUG_LOG_LEVEL()) { + PrintResultSlot(invokeExecState->m_resultSlot); + } + } + + // cleanup + if (SPI_tuptable != nullptr) { + SPI_freetuptable(SPI_tuptable); + SPI_tuptable = nullptr; + } + return rc; +} + +static int JitExecNonJitSubQuery(int subQueryId, int tcount) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile int result = 0; + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile bool isSpiPushed = false; + volatile int spiConnectId = SPI_connectid(); + + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + JitExec::JitFunctionContext* functionContext = (JitExec::JitFunctionContext*)jitContext; + JitExec::JitCallSite* callSite = &functionContext->m_SPSubQueryList[subQueryId]; + JitExec::JitInvokedQueryExecState* invokeExecState = &functionExecState->m_invokedQueryExecState[subQueryId]; + bool planSourceHadContext = false; + if (invokeExecState->m_plan != nullptr && invokeExecState->m_plan->plancache_list != NIL) { + CachedPlanSource* planSource = (CachedPlanSource*)linitial(invokeExecState->m_plan->plancache_list); + if (planSource->mot_jit_context != nullptr) { + planSourceHadContext = true; + } + } + + // get a valid query string + const char* queryString = callSite->m_queryString; + if ((queryString == nullptr) && (callSite->m_queryContext != nullptr)) { + queryString = callSite->m_queryContext->m_queryString; + } + MOT_ASSERT(queryString != nullptr); + + MOT_LOG_DEBUG("Executing non-jittable sub-query: %s", queryString) + jitContext->m_execState->m_sqlState = 0; + PG_TRY(); + { + // run sub-query to completion, in strict execution it is expected to return only one tuple, otherwise none. + MOT_LOG_DEBUG("Invoking SP non-jittable sub-query %d with params: %p", subQueryId, invokeExecState->m_params); + + // we push SPI context only if we expect to call another SP, + // otherwise we continue working in current memory context + if (callSite->m_isUnjittableInvoke) { + MOT_LOG_DEBUG("Calling SPI_push()") + SPI_push(); + isSpiPushed = true; + } + + // ensure we have a prepared SPI plan + if (!RecompilePlanIfNeeded(functionContext, subQueryId, invokeExecState)) { + MOT_LOG_TRACE("Failed to recompile SPI plan for non-jittable sub-query %u: %s", subQueryId, queryString); + // throw ereport to get proper error handling + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_PLPGSQL_ERROR), + errmsg("Failed to recompile SPI plan for non-jittable sub-query %u: %s", + subQueryId, + callSite->m_queryString))); + } else if (invokeExecState->m_plan == nullptr) { + MOT_LOG_TRACE("Found unprepared SPI plan for non-jittable sub-query %u: %s", subQueryId, queryString); + // throw ereport to get proper error handling + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_PLPGSQL_ERROR), + errmsg("Found unprepared SPI plan for non-jittable sub-query %u: %s", subQueryId, queryString))); + } else { + // prepare invoke parameters + PrepareInvokeParams(functionContext, functionExecState, invokeExecState, subQueryId); + + // now execute the plan + (void)::ExecClearTuple(invokeExecState->m_resultSlot); + result = ExecNonJitSubQueryPlan(callSite, invokeExecState, tcount, subQueryId); + if (result != 0) { + // throw ereport to get proper error handling + MOT_LOG_TRACE("Failed to execute non-jittable sub-query %u: %s", subQueryId, queryString); + if (jitContext->m_execState->m_sqlState == 0) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_PLPGSQL_ERROR), + errmsg("Failed to execute non-jittable sub-query %u: %s", subQueryId, queryString))); + } else { + signalException = true; + } + } + MOT_LOG_DEBUG("Finished executing non-jittable SP sub-query: %s", queryString); + } + + // ATTENTION: it is possible that we arrived here through a jittable sub-query in intermediate state + // (e.g. pending compile), and due to revalidation the jit context was deleted in the plan, so we must update + // the call site, otherwise we will crash during context destruction + CachedPlanSource* planSource = (CachedPlanSource*)linitial(invokeExecState->m_plan->plancache_list); + if ((planSourceHadContext) && (planSource->mot_jit_context == nullptr) && + (callSite->m_queryContext != nullptr)) { + // transform into non-jittable sub-query + // the query string is safe to duplicate, as it comes from the JIT source + callSite->m_queryContext = nullptr; + if (callSite->m_queryString == nullptr) { + callSite->m_queryString = JitExec::DupString(queryString, JitExec::JitContextUsage::JIT_CONTEXT_LOCAL); + if (callSite->m_queryString == nullptr) { + ereport(ERROR, + (errmodule(MOD_MOT), + errmsg("Failed to allocate local memory for query string: %s", queryString))); + } + } + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + HandleSubQueryError(jitContext->m_execState, false, spiConnectId); + isSpiPushed = false; // SPI connection already restored to original state in HandlePGError() + signalException = true; + } + PG_END_TRY(); + + u_sess->mot_cxt.jit_txn->FinishStatement(); + + if (isSpiPushed) { + SPI_pop(); + } + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + + // ATTENTION: original SPI result is passed to caller for further inspection through the execution state of the + // backing context + return result; +} + +void JitReleaseNonJitSubQueryResources(int subQueryId) +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + JitExec::JitFunctionContext* functionContext = (JitExec::JitFunctionContext*)jitContext; + JitExec::JitCallSite* callSite = &functionContext->m_SPSubQueryList[subQueryId]; + JitExec::JitQueryContext* subQueryContext = (JitExec::JitQueryContext*)callSite->m_queryContext; + if (subQueryContext == nullptr) { + if (SPI_tuptable != nullptr) { + SPI_freetuptable(SPI_tuptable); + SPI_tuptable = nullptr; + } + } +} + +int JitExecSubQuery(int subQueryId, int tcount) +{ + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + JitExec::JitFunctionContext* functionContext = (JitExec::JitFunctionContext*)jitContext; + JitExec::JitCallSite* callSite = &functionContext->m_SPSubQueryList[subQueryId]; + volatile JitExec::JitQueryContext* subQueryContext = (JitExec::JitQueryContext*)callSite->m_queryContext; + + MOT_LOG_DEBUG("Executing sub-query %d with tcount %d", subQueryId, tcount); + if ((subQueryContext == nullptr) || IsJitContextPendingCompile((JitExec::MotJitContext*)subQueryContext) || + IsJitContextErrorCompile((JitExec::MotJitContext*)subQueryContext)) { + MOT_LOG_DEBUG("Executing non-jittable sub-query %u: %s", subQueryId, callSite->m_queryString); + return JitExecNonJitSubQuery(subQueryId, tcount); + } + + // if context is invalid it will be revalidated when we acquire locks, and if that fails we will transform into + // non-jittable sub-query + MOT_LOG_DEBUG("Executing jittable sub-query %u: %s", subQueryId, subQueryContext->m_queryString); + + // ATTENTION: current sub-query id must be exposed so that result collection can take place + functionExecState->m_currentSubQueryId = subQueryId; + + // function parameters already passed into sub-query parameters by previous instructions + + // ATTENTION: sub-query result slot is maintained in sub-query exec state + + // ATTENTION: current call might generate ereport that will end entire function execution, so we need to catch it + // and translate it so it an be caught by exception handler block in jitted code + jitContext->m_execState->m_sqlState = 0; + subQueryContext->m_execState->m_sqlState = 0; + MOT_LOG_DEBUG("(Before sub-query) Current sub-transaction #%" PRIu64 ": id %" PRIu64, + functionExecState->m_openSubTxCount, + (uint64_t)::GetCurrentSubTransactionId()); + + // in case we hit parsing during revalidate, we must make sure we have up-to-date parameters + volatile JitExec::JitInvokedQueryExecState* invokeExecState = + &functionExecState->m_invokedQueryExecState[subQueryId]; + invokeExecState->m_params->parserSetupArg = (void*)invokeExecState->m_expr; + functionExecState->m_estate.cur_expr = invokeExecState->m_expr; // required by plpgsql_param_fetch + if (invokeExecState->m_expr != nullptr) { + invokeExecState->m_expr->func = functionExecState->m_function; + invokeExecState->m_expr->func->cur_estate = (PLpgSQL_execstate*)&functionExecState->m_estate; + } + MOT_LOG_DEBUG("Invoking SP sub-query %d with params: %p", subQueryId, invokeExecState->m_params); + invokeExecState->m_spiResult = SPI_OK_SELECT; + + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile int result = 0; + volatile bool signalException = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile SubTransactionId subXid = ::GetCurrentSubTransactionId(); + volatile CachedPlan* cplan = nullptr; + volatile bool planTransformed = false; + volatile bool planExecuteAsNonJit = false; + volatile bool pushedActiveSnapshot = false; + volatile bool isSpiPushed = false; + volatile int spiConnectId = SPI_connectid(); + + // run sub-query to completion, in strict execution it is expected to return only one tuple, otherwise none. + PG_TRY(); + { + // acquire plan locks before execution - this may trigger JIT context revalidation (this is good) + // attention: we do not need full analysis, just revalidation and parse + if (subQueryContext->m_commandType != JitExec::JIT_COMMAND_INVOKE) { + // we need to make sure the plan source points to the jit context to be executed, so revalidation occurs + const char* queryString = subQueryContext->m_jitSource->m_queryString; + MOT_ASSERT(list_length(invokeExecState->m_plan->plancache_list) == 1); + CachedPlanSource* planSource = (CachedPlanSource*)linitial(invokeExecState->m_plan->plancache_list); + MOT_ASSERT(planSource->mot_jit_context == nullptr || (planSource->mot_jit_context == subQueryContext)); + if (planSource->mot_jit_context == nullptr) { + planSource->mot_jit_context = (JitExec::MotJitContext*)subQueryContext; + } + + cplan = SPI_plan_get_cached_plan(invokeExecState->m_plan); + if (cplan == nullptr) { + MOT_LOG_ERROR("Failed to get cached plan"); + } + + // it is possible at this point that query got disqualified and is not jittable now + if (planSource->mot_jit_context == nullptr) { + // we transform this sub-query into a non-jittable one and then execute it + // the query string is safe to duplicate, as it comes from the JIT source + MOT_LOG_TRACE("Jitted sub-query became unjittable after revalidation: %s", queryString); + callSite->m_queryString = JitExec::DupString(queryString, JitExec::JitContextUsage::JIT_CONTEXT_LOCAL); + if (callSite->m_queryString == nullptr) { + ereport(ERROR, + (errmodule(MOD_MOT), + errmsg("Failed to allocate local memory for query string: %s", queryString))); + } + callSite->m_queryContext = nullptr; // already deleted during revalidation + planTransformed = true; + } else if (!JitExec::IsJitContextValid(planSource->mot_jit_context)) { + MOT_LOG_TRACE("Jitted sub-query revalidation failed, executing as non-jittable: %s", queryString); + planExecuteAsNonJit = true; + } + } else { + if (IsJitContextDoneCompile((JitExec::MotJitContext*)subQueryContext)) { + MOT_LOG_TRACE("Revalidate sub-query %d after compile-done: %s", + subQueryId, + subQueryContext->m_jitSource->m_queryString); + if (!RevalidateJitContext((JitExec::MotJitContext*)subQueryContext)) { + MOT_LOG_TRACE("Revalidate sub-query %d after compile-done failed, executing as non-jittable: %s", + subQueryId, + subQueryContext->m_jitSource->m_queryString); + planExecuteAsNonJit = true; + } + } + } + + if (planTransformed || planExecuteAsNonJit) { + if (planTransformed) { + MOT_ASSERT(callSite->m_queryString != nullptr); + MOT_LOG_TRACE("Executing transformed SP query as non-jittable: %s", callSite->m_queryString); + } else { + MOT_LOG_TRACE( + "Executing invalid SP query as non-jittable: %s", callSite->m_queryContext->m_queryString); + } + result = JitExecNonJitSubQuery(subQueryId, tcount); + } else { + PushActiveSnapshot(GetTransactionSnapshot()); + pushedActiveSnapshot = false; + + // we push SPI context only if we expect to call another SP, + // otherwise we continue working in current memory context + if (subQueryContext->m_commandType == JitExec::JIT_COMMAND_INVOKE) { + SPI_push(); + isSpiPushed = true; + } + + // run sub-query to completion, in strict execution it is expected to return only one tuple, otherwise none. + JitExec::JitInvokedQueryExecState* subQueryExecState = + &functionExecState->m_invokedQueryExecState[subQueryId]; + MOT_LOG_DEBUG("Invoking SP sub-query %d with params: %p", subQueryId, subQueryExecState->m_params); + + // pass parameters coming from stored procedure parameters, some of which may be directly passed from the + // query that invoked the stored procedure + PrepareInvokeParams( + functionContext, (JitExec::JitFunctionExecState*)functionExecState, subQueryExecState, subQueryId); + + // setup invoking context + subQueryContext->m_execState->m_invokingContext = functionContext; + + // make sure result is cleared - so caller can tell zero tuples were retrieved + (void)::ExecClearTuple(subQueryExecState->m_resultSlot); + JitResetScan((JitExec::MotJitContext*)subQueryContext); // signal new scan is starting + if ((subQueryContext->m_commandType == JitExec::JIT_COMMAND_RANGE_SELECT) || + (subQueryContext->m_commandType == JitExec::JIT_COMMAND_RANGE_JOIN)) { + unsigned long nprocessed = 0; + // save only first slot for strict execution, the rest can be overwritten on work slot + TupleTableSlot* slot = subQueryExecState->m_resultSlot; + bool finish = false; + while (!finish) { + uint64_t tuplesProcessed = 0; + result = JitExec::JitExecQuery((JitExec::MotJitContext*)subQueryContext, + subQueryExecState->m_params, + slot, + &tuplesProcessed, + &subQueryExecState->m_scanEnded); + if (subQueryExecState->m_scanEnded || (tuplesProcessed == 0) || (result != 0)) { + // raise flag so that next round we will bail out (current tuple still must be reported to user) + finish = true; + } + if (tuplesProcessed > 0) { + ++nprocessed; + if ((tcount != 0) && (nprocessed == (unsigned long)tcount)) { + finish = true; + } + } + // copy first slot if needed and switch to work slot from now on + if (slot == subQueryExecState->m_resultSlot) { + // we need to copy slot if there is a minimal tuple or SPI push was called and the tuple is not + // empty + if ((slot->tts_mintuple != nullptr) || (isSpiPushed && !slot->tts_isempty)) { + CopyTupleTableSlot(subQueryExecState, subQueryExecState->m_resultSlot); + } + } + slot = subQueryExecState->m_workSlot; + } + subQueryExecState->m_tuplesProcessed = nprocessed; + if ((nprocessed > 0) && (result == MOT::RC_LOCAL_ROW_NOT_FOUND)) { + // if the first rounds were fine and retrieved rows, but the last iteration found no row, then we + // should not report any error - this is normal + result = MOT::RC_OK; + } + } else { + // in this case we disregard tcount, since this is a point query, or even insert/delete/update + result = JitExec::JitExecQuery((JitExec::MotJitContext*)subQueryContext, + subQueryExecState->m_params, + subQueryExecState->m_resultSlot, + &subQueryExecState->m_tuplesProcessed, + &subQueryExecState->m_scanEnded); + } + + CommandCounterIncrement(); + PopActiveSnapshot(); + pushedActiveSnapshot = false; + + MOT_LOG_DEBUG("Finished executing jitted SP sub-query with %" PRIu64 " tuples and result: %s", + subQueryExecState->m_tuplesProcessed, + MOT::RcToString((MOT::RC)result)); + if ((result != MOT::RC_OK) && (result != MOT::RC_LOCAL_ROW_NOT_FOUND)) { + // throw ereport to get proper error handling + MOT_LOG_TRACE( + "Failed to execute jittable sub-query %u: %s", subQueryId, subQueryContext->m_queryString); + if (subQueryContext->m_execState->m_sqlState == 0) { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_PLPGSQL_ERROR), + errmsg("Failed to execute jittable sub-query %u: %s", + subQueryId, + subQueryContext->m_queryString))); + } + } else if (result != MOT::RC_LOCAL_ROW_NOT_FOUND) { + if (MOT_CHECK_DEBUG_LOG_LEVEL()) { + PrintResultSlot(subQueryExecState->m_resultSlot); + } + } + // cleanup SPI stack if needed before handling any error + if (isSpiPushed) { + SPI_pop(); + isSpiPushed = false; + } + } + + // release plan - locks are released by resource owner during commit! + if (cplan != nullptr) { + ReleaseCachedPlan((CachedPlan*)cplan, invokeExecState->m_plan->saved); + } + } + PG_CATCH(); + { + // first restore memory context + CurrentMemoryContext = origCxt; + + // release cached plan or planner locks + if (cplan != nullptr) { + ReleaseCachedPlan((CachedPlan*)cplan, invokeExecState->m_plan->saved); + } + + // print error + HandleSubQueryError(subQueryContext->m_execState, true, spiConnectId); + isSpiPushed = false; // SPI connection already restored to original state in HandlePGError() + signalException = true; + + if (pushedActiveSnapshot) { + PopActiveSnapshot(); + } + + // in debug mode we validate sub-tx state + SubTransactionId endSubXid = ::GetCurrentSubTransactionId(); + if (subXid != endSubXid) { + MOT_LOG_WARN("Invalid sub-tx state: entered with %" PRIu64 ", exited with " PRIu64, subXid, endSubXid); + } + MOT_ASSERT(subXid == endSubXid); + // rollback all open sub-txns until the one we started with + } + PG_END_TRY(); + + MOT_ASSERT(!isSpiPushed); + // pull-up error info + if (!planTransformed && !planExecuteAsNonJit) { + PullUpErrorInfo(subQueryContext->m_execState, jitContext->m_execState); + u_sess->mot_cxt.jit_txn->FinishStatement(); + } + + // reset sub-query id + functionExecState->m_currentSubQueryId = -1; + + if (!planTransformed && !planExecuteAsNonJit && signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + + // return execution result + return result; +} + +int JitGetTuplesProcessed(int subQueryId) +{ + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + JitExec::JitInvokedQueryExecState* invokeExecState = &functionExecState->m_invokedQueryExecState[subQueryId]; + return invokeExecState->m_tuplesProcessed; +} + +Datum JitGetSubQuerySlotValue(int subQueryId, int tupleColId) +{ + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + TupleTableSlot* slot = functionExecState->m_invokedQueryExecState[subQueryId].m_resultSlot; + return GetSlotValue(slot, tupleColId); +} + +int JitGetSubQuerySlotIsNull(int subQueryId, int tupleColId) +{ + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + TupleTableSlot* slot = functionExecState->m_invokedQueryExecState[subQueryId].m_resultSlot; + return GetSlotIsNull(slot, tupleColId); +} + +HeapTuple JitGetSubQueryResultHeapTuple(int subQueryId) +{ + HeapTuple heapTuple = (HeapTuple)DatumGetPointer(JitGetSubQuerySlotValue(subQueryId, 0)); + MOT_LOG_DEBUG("Retrieving heap tuple for sub-query %d at %p", subQueryId, heapTuple); + return heapTuple; +} + +Datum JitGetHeapTupleValue(HeapTuple tuple, int subQueryId, int columnId, int* isNull) +{ + MOT_ASSERT(u_sess->mot_cxt.jit_context->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitExec::JitFunctionContext* jitContext = (JitExec::JitFunctionContext*)u_sess->mot_cxt.jit_context; + MOT_ASSERT(subQueryId < (int)jitContext->m_SPSubQueryCount); + JitExec::MotJitContext* subContext = jitContext->m_SPSubQueryList[subQueryId].m_queryContext; + MOT_ASSERT(subContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_QUERY); + MOT_ASSERT(subContext->m_commandType == JitExec::JIT_COMMAND_INVOKE); + TupleDesc tupDesc = ((JitExec::JitQueryContext*)subContext)->m_invokeContext->m_resultTupDesc; + + MOT_LOG_DEBUG("Retrieving sub-query %d heap tuple %p attribute %d using tuple descriptor %p", + subQueryId, + tuple, + columnId, + tupDesc); + bool isNullBool = false; + Datum value = SPI_getbinval(tuple, tupDesc, columnId + 1, &isNullBool); + *isNull = isNullBool ? 1 : 0; + DEBUG_PRINT_DATUM("Datum: ", tupDesc->attrs[columnId]->atttypid, value, isNullBool); + return value; +} + +int JitGetSpiResult(int subQueryId) +{ + JitExec::JitFunctionExecState* functionExecState = GetFunctionExecStateVerify(subQueryId); + JitExec::JitInvokedQueryExecState* invokeExecState = &functionExecState->m_invokedQueryExecState[subQueryId]; + int rc = invokeExecState->m_spiResult; + MOT_LOG_DEBUG("Returning sub-query SPI result %d: %s", rc, SPI_result_code_string(rc)); + return rc; +} + +inline Datum JitConvertViaStringImpl(Datum value, Oid resultType, Oid targetType, int typeMod) +{ + // convert result type to target type via string + // convert result to string using type output function + Oid typeOutput; + bool typeIsVarlena = false; + getTypeOutputInfo(resultType, &typeOutput, &typeIsVarlena); + char* valueStr = OidOutputFunctionCall(typeOutput, value); + MOT_LOG_DEBUG("JitConvertViaString(): Intermediate str result = %s", valueStr); + + // convert string to target type using type input function + Oid typeInput; + Oid typeIoParam; + getTypeInputInfo(targetType, &typeInput, &typeIoParam); + Datum result = OidInputFunctionCall(typeInput, valueStr, typeIoParam, typeMod); + pfree_ext(valueStr); + + return result; +} + +Datum JitConvertViaString(Datum value, Oid resultType, Oid targetType, int typeMod) +{ + MOT_LOG_DEBUG("JitConvertViaString() resultType = %d, targetType=%d, typeMod=%d", resultType, targetType, typeMod); + + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + volatile JitExec::JitFunctionExecState* execState = (JitExec::JitFunctionExecState*)jitContext->m_execState; + volatile MemoryContext origCxt = (execState->m_spiBlockId >= 1) + ? MemoryContextSwitchTo(execState->m_savedContexts[0]) + : JitExec::SwitchToSPICallerContext(); + volatile int spiConnectId = SPI_connectid(); + PG_TRY(); + { + result = JitConvertViaStringImpl(value, resultType, targetType, typeMod); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + if (origCxt != nullptr) { + (void)MemoryContextSwitchTo(origCxt); + } + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + return result; +} + +static Datum JitCastValueImpl(Datum value, Oid sourceType, Oid targetType, int typeMod, CoercionPathType coercePath, + Oid funcId, CoercionPathType coercePath2, Oid funcId2, int nargs, bool& exceptionSignaled) +{ + // temporary calculation made on current memory context + volatile Datum result = PointerGetDatum(nullptr); + volatile bool signalException = false; + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile int spiConnectId = SPI_connectid(); + PG_TRY(); + { + MOT_ASSERT((targetType != sourceType) || (typeMod != -1)); + if ((funcId != InvalidOid) && + !(coercePath == COERCION_PATH_COERCEVIAIO || coercePath == COERCION_PATH_ARRAYCOERCE)) { + // call convert once + result = OidFunctionCall1(funcId, value); + if ((coercePath2 == COERCION_PATH_FUNC) && OidIsValid(funcId2)) { + if (nargs == 1) { + result = OidFunctionCall1(funcId2, result); + } else if (nargs == 2) { + result = OidFunctionCall2(funcId2, result, typeMod); + } else if (nargs == 3) { + result = OidFunctionCall3(funcId2, result, typeMod, 0); + } else { + ereport(ERROR, + (errmodule(MOD_MOT), + errcode(ERRCODE_OUT_OF_LOGICAL_MEMORY), + errmsg("Unexpected number of arguments for conversion function: %u", funcId))); + } + } + } else { + result = JitConvertViaStringImpl(value, sourceType, targetType, typeMod); + } + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + if (origCxt != nullptr) { + (void)MemoryContextSwitchTo(origCxt); + } + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + exceptionSignaled = true; + } + + return result; +} + +Datum JitCastValue(Datum value, Oid sourceType, Oid targetType, int typeMod, int coercePath, Oid funcId, + int coercePath2, Oid funcId2, int nargs, int typeByVal) +{ + volatile Datum result = PointerGetDatum(nullptr); + bool exceptionSignaled = false; + + MOT_LOG_DEBUG("Casting value from type %u to type %u by typeMod %d, typeByVal = %d", + sourceType, + targetType, + typeMod, + typeByVal); + + // make the cast + if ((targetType != sourceType) || (typeMod != -1)) { + result = JitCastValueImpl(value, + sourceType, + targetType, + typeMod, + (CoercionPathType)coercePath, + funcId, + (CoercionPathType)coercePath2, + funcId2, + nargs, + exceptionSignaled); + + // check if exception occurred + if (exceptionSignaled) { + return result; + } + } else { + // direct assignment - no cast needed (impossible when typeByVal == 0, because in this case we would not emit + // call to JitCastValue in the first place...) + MOT_ASSERT(!typeByVal); + result = value; + } + + // if type is passed by value we are done + if (typeByVal) { + return result; + } + + // copy datum to caller's memory context + volatile JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext->m_contextType == JitExec::JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + volatile JitExec::JitFunctionExecState* execState = (JitExec::JitFunctionExecState*)jitContext->m_execState; + volatile MemoryContext origCxt = (execState->m_spiBlockId >= 1) + ? MemoryContextSwitchTo(execState->m_savedContexts[0]) + : JitExec::SwitchToSPICallerContext(); + volatile int spiConnectId = SPI_connectid(); + volatile bool signalException = false; + PG_TRY(); + { + result = JitExec::CopyDatum(result, targetType, false); + } + PG_CATCH(); + { + // switch back to original context before issuing an error report + (void)MemoryContextSwitchTo(origCxt); + HandlePGFunctionError((JitExec::MotJitContext*)jitContext, spiConnectId); + signalException = true; + } + PG_END_TRY(); + if (origCxt != nullptr) { + (void)MemoryContextSwitchTo(origCxt); + } + + if (signalException) { + SignalException((JitExec::MotJitContext*)jitContext); + } + + return result; +} + +void JitSaveErrorInfo(Datum errorMessage, int sqlState, Datum sqlStateString) +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext != nullptr); + JitExec::JitExecState* execState = jitContext->m_execState; + MOT_ASSERT(execState != nullptr); + execState->m_errorMessage = errorMessage; + execState->m_sqlState = sqlState; + execState->m_sqlStateString = sqlStateString; +} + +Datum JitGetErrorMessage() +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext != nullptr); + JitExec::JitExecState* execState = jitContext->m_execState; + MOT_ASSERT(execState != nullptr); + return execState->m_errorMessage; +} + +int JitGetSqlState() +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext != nullptr); + JitExec::JitExecState* execState = jitContext->m_execState; + MOT_ASSERT(execState != nullptr); + return execState->m_sqlState; +} + +Datum JitGetSqlStateString() +{ + JitExec::MotJitContext* jitContext = u_sess->mot_cxt.jit_context; + MOT_ASSERT(jitContext != nullptr); + JitExec::JitExecState* execState = jitContext->m_execState; + MOT_ASSERT(execState != nullptr); + return execState->m_sqlStateString; +} + +Datum JitGetDatumIsNotNull(int isNull) +{ + return BoolGetDatum(!isNull ? true : false); +} + +void EmitProfileData(uint32_t functionId, uint32_t regionId, int beginRegion) +{ + JitExec::JitProfiler::GetInstance()->EmitProfileData(functionId, regionId, beginRegion != 0); } } // extern "C" diff --git a/src/gausskernel/storage/mot/jit_exec/jit_helpers.h b/src/gausskernel/storage/mot/jit_exec/jit_helpers.h index 543fb249a..b2892144d 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_helpers.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_helpers.h @@ -25,18 +25,22 @@ #ifndef JIT_HELPERS_H #define JIT_HELPERS_H -#include "jit_pgproc.h" +#include "postgres.h" +#include "nodes/params.h" #include "mot_engine.h" /*--------------------------- LLVM Access Helpers ---------------------------*/ extern "C" { - /** * @brief Issue debug log message. * @param function The function name. * @param msg The log message. */ void debugLog(const char* function, const char* msg); +void debugLogInt(const char* msg, int arg); +void debugLogString(const char* msg, const char* arg); +void debugLogStringDatum(const char* msg, int64_t arg); +void debugLogDatum(const char* msg, Datum value, int isNull, int type); /*--------------------------- Engine Access Helpers ---------------------------*/ /** @@ -82,46 +86,54 @@ void InitKey(MOT::Key* key, MOT::Index* index); MOT::Column* getColumnAt(MOT::Table* table, int table_colid); /** - * @brief Marks expression evaluated to null or not in the specified argument position. - * @param arg_pos The ordinal position of the expression. + * @brief Marks expression evaluated to null or not. * @param isnull Specifies whether the expression evaluated to null. */ -void setExprArgIsNull(int arg_pos, int isnull); +void SetExprIsNull(int isnull); /** - * @brief Queries whether an expression in the specified argument position evaluated to null. - * @param arg_pos The ordinal position of the expression. - * @return Non-zero value if the expression in the specified argument position evaluated to null, - * otherwise zero. + * @brief Queries whether an expression evaluated to null. + * @return Non-zero value if the expression evaluated to null, otherwise zero. */ -int getExprArgIsNull(int arg_pos); +int GetExprIsNull(); + +/** @brief Sets last evaluated expression collation. */ +void SetExprCollation(int collationId); + +/** @brief Retrieves last evaluated expression collation. */ +int GetExprCollation(); /** * @brief Retrieves a pooled constant by its identifier. * @param constId The identifier of the constant value. - * @param argPos The ordinal position of the enveloping parameter expression. * @return The constant value. */ -Datum GetConstAt(int constId, int argPos); +Datum GetConstAt(int constId); /** * @brief Retrieves a datum parameter from parameters array. * @param params The parameter array. * @param paramid The zero-based index of the parameter to retrieve. - * @param arg_pos The ordinal position of the enveloping parameter expression. * @return The parameter value. */ -Datum getDatumParam(ParamListInfo params, int paramid, int arg_pos); +Datum getDatumParam(ParamListInfo params, int paramid); /** * @brief Reads a column value from a row. * @param table The originating table of the row. * @param row The row from which the column value is to be retrieved. * @param colid The zero-based index of the column to retrieve. - * @param arg_pos The ordinal position of the enveloping VAR expression. + * @param innerRow Specified whether datum is read from inner row or not (relevant only for direct row access). + * @param subQueryIndex Specifies whether datum is read from sub=query result (relevant only for direct row access). * @return The column value. */ -Datum readDatumColumn(MOT::Table* table, MOT::Row* row, int colid, int arg_pos); +Datum readDatumColumn(MOT::Table* table, MOT::Row* row, int colid, int innerRow, int subQueryIndex); + +/** + * @brief Queries whether the directly-accessed row was concurrently deleted. + * @return Non-zero value if the row was deleted. + */ +int IsDirectRowDeleted(int innerRow); /** * @brief Writes a column value into a row. @@ -145,28 +157,18 @@ void buildDatumKey( MOT::Column* column, MOT::Key* key, Datum value, int index_colid, int offset, int size, int value_type); /*--------------------------- Invoke PG Operators ---------------------------*/ -// cast operators retain first null parameter as null result -// other operator will crash if null is provided -#define APPLY_UNARY_OPERATOR(funcid, name) Datum invoke_##name(Datum arg, int arg_pos); +Datum JitInvokePGFunction0(PGFunction fptr, int collationId); +Datum JitInvokePGFunction1(PGFunction fptr, int collationId, int isStrict, Datum arg, int isnull, Oid argType); +Datum JitInvokePGFunction2(PGFunction fptr, int collationId, int isStrict, Datum arg1, int isnull1, Oid argType1, + Datum arg2, int isnull2, Oid argType2); +Datum JitInvokePGFunction3(PGFunction fptr, int collationId, int isStrict, Datum arg1, int isnull1, Oid argType1, + Datum arg2, int isnull2, Oid argType2, Datum arg3, int isnull3, Oid argType3); +Datum JitInvokePGFunctionN( + PGFunction fptr, int collationId, int isStrict, Datum* args, int* isnull, Oid* argTypes, int argCount); -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) Datum invoke_##name(Datum arg, int arg_pos); - -#define APPLY_BINARY_OPERATOR(funcid, name) Datum invoke_##name(Datum lhs, Datum rhs, int arg_pos); - -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) Datum invoke_##name(Datum lhs, Datum rhs, int arg_pos); - -#define APPLY_TERNARY_OPERATOR(funcid, name) Datum invoke_##name(Datum arg1, Datum arg2, Datum arg3, int arg_pos); - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) Datum invoke_##name(Datum arg1, Datum arg2, Datum arg3, int arg_pos); - -APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR +/*--------------------------- Memory Allocation ---------------------------*/ +uint8_t* JitMemSessionAlloc(uint32_t size); +void JitMemSessionFree(uint8_t* ptr); /*--------------------------- BitmapSet Helpers ---------------------------*/ /** @@ -203,9 +205,11 @@ int writeRow(MOT::Row* row, MOT::BitmapSet* bmp); * @param table The table to search. * @param key The key by which the row is to be searched. * @param access_mode_value The required row access mode. + * @param innerRow Specified whether searching for an inner row (relevant only for direct row access). + * @param subQueryIndex Specifies whether searching for a sub-query row (relevant only for direct row access). * @return The requested row, or NULL if the row was not found, or some other failure occurred. */ -MOT::Row* searchRow(MOT::Table* table, MOT::Key* key, int access_mode_value); +MOT::Row* searchRow(MOT::Table* table, MOT::Key* key, int access_mode_value, int innerRow, int subQueryIndex); /** * @brief Creates a new table row. @@ -235,6 +239,8 @@ int deleteRow(); */ void setRowNullBits(MOT::Table* table, MOT::Row* row); +int setConstNullBit(MOT::Table* table, MOT::Row* row, int table_colid, int isnull); + /** * @brief Sets the null bit of a row column according to the lastly evaluated expression. * @param table The table to which the row belongs. @@ -309,8 +315,8 @@ void adjustKey(MOT::Key* key, MOT::Index* index, unsigned char pattern); * @brief Searches an iterator for a range scan. * @param index The index in which to search the iterator. * @param key The search key. - * @param forward_scan Specifies whether this is a forward scan. If true then a forward iterator is - * created (such that the an iterator pointing to the value succeeding the lower bound is returned). + * @param forward_scan Specifies whether this is a forward scan. If true then a forward iterator is created (such that + * the an iterator pointing to the value succeeding the lower bound is returned). * @return The resulting iterator, or NULL if failed. */ MOT::IndexIterator* searchIterator(MOT::Index* index, MOT::Key* key, int forward_scan, int include_bound); @@ -326,8 +332,8 @@ MOT::IndexIterator* beginIterator(MOT::Index* index); * @brief Creates an end-iterator for a range scan. * @param index The index in which to search the iterator. * @param key The search key. - * @param forward_scan Specifies whether this is a forward scan. If true then a reverse iterator is - * created (such that the an iterator pointing to the value preceeding the upper bound is returned). + * @param forward_scan Specifies whether this is a forward scan. If true then a reverse iterator is created (such that + * the iterator pointing to the value preceding the upper bound is returned). * @return The resulting iterator, or NULL if failed. */ MOT::IndexIterator* createEndIterator(MOT::Index* index, MOT::Key* key, int forward_scan, int include_bound); @@ -343,16 +349,28 @@ MOT::IndexIterator* createEndIterator(MOT::Index* index, MOT::Key* key, int forw int isScanEnd(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int forward_scan); /** - * @brief Retrieves a row from an iterator during a range scan. + * @brief Checks whether there is another row during range scan. Cursor is advanced. + * @param index The index on which the range scan is performed. + * @param itr The begin iterator. + * @param endItr The end iterator. + * @param forwardScan Specifies whether this is a forward scan. + * @return Non-zero value if a row was found, or zero if end of scan reached, or some other failure occurred. + */ +int CheckRowExistsInIterator(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* endItr, int forwardScan); + +/** + * @brief Retrieves a row from an iterator during a range scan. Cursor is advanced. * @param index The index on which the range scan is performed. * @param itr The begin iterator. * @param end_itr The end iterator. * @param access_mode The required row access mode. * @param forward_iterator Specifies whether this is a forward scan. + * @param innerRow Specifies whether searching for inner row (relevant only for direct row access). + * @param subQueryIndex Specifies whether searching for a sub-query row (relevant only for direct row access). * @return The resulting row, or NULL if end of scan reached, or some other failure occurred. */ -MOT::Row* getRowFromIterator( - MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int access_mode, int forward_scan); +MOT::Row* getRowFromIterator(MOT::Index* index, MOT::IndexIterator* itr, MOT::IndexIterator* end_itr, int access_mode, + int forward_scan, int innerRow, int subQueryIndex); /** * @brief Destroys an iterator. @@ -472,46 +490,42 @@ int getStateLimitCounter(); * @param element_type The Oid of the element type of the array. * @param element_count The number of elements in the array. */ -void prepareAvgArray(int element_type, int element_count); +void prepareAvgArray(int aggIndex, int element_type, int element_count); /** @brief Saves an intermediate AVG() aggregate operator accumulation. */ -Datum loadAvgArray(); +Datum loadAvgArray(int aggIndex); /** @brief Saves an intermediate AVG() aggregate operator accumulation. */ -void saveAvgArray(Datum avg_array); +void saveAvgArray(int aggIndex, Datum avg_array); /** @brief Computes the average value from a final AVG() aggregate operator accumulation. */ -Datum computeAvgFromArray(int element_type); +Datum computeAvgFromArray(int aggIndex, int element_type); /*--------------------------- Generic Aggregate Helpers ---------------------------*/ /** @brief Initializes a aggregated value to zero. */ -void resetAggValue(int element_type); +void resetAggValue(int aggIndex, int element_type); /** @brief Retrieves the value of the aggregated value used to implement SUM/MAX/MIN/COUNT() operators. */ -Datum getAggValue(); +Datum getAggValue(int aggIndex); /** @brief Retrieves the value of the aggregated value used to implement SUM/MAX/MIN/COUNT() operators. */ -void setAggValue(Datum new_value); +void setAggValue(int aggIndex, Datum new_value); -/*--------------------------- Min/Max Aggregate Helpers ---------------------------*/ -/** @brief Resets the flag recording whether a MAX/MIN value was aggregated (or it is still null). */ -void resetAggMaxMinNull(); +/** @brief Retrieves the flag recording whether SUM/MAX/MIN/COUNT() value was aggregated (or it is still null). */ +int getAggValueIsNull(int aggIndex); -/** @brief Records that a MAX/MIN value was aggregated. */ -void setAggMaxMinNotNull(); - -/** @brief Retrieves the flag recording whether a MAX/MIN value was aggregated (or it is still null). */ -int getAggMaxMinIsNull(); +/** @brief Sets the flag recording whether SUM/MAX/MIN/COUNT() value was aggregated (or it is still null). */ +int setAggValueIsNull(int aggIndex, int isNull); /*--------------------------- Distinct Aggregate Helpers ---------------------------*/ /** @brief Prepares a set of distinct items for DISTINCT() operator. */ -void prepareDistinctSet(int element_type); +void prepareDistinctSet(int aggIndex, int element_type); /** @brief Adds an items to the distinct item set. Returns non-zero value if item was added. */ -int insertDistinctItem(int element_type, Datum item); +int insertDistinctItem(int aggIndex, int element_type, Datum item); /** @brief Destroys the set of distinct items for DISTINCT() operator. */ -void destroyDistinctSet(int element_type); +void destroyDistinctSet(int aggIndex, int element_type); /** * @brief Resets a tuple datum to zero value. @@ -525,18 +539,18 @@ void resetTupleDatum(TupleTableSlot* slot, int tupleColumnId, int zeroType); * @brief Reads a tuple datum. * @param slot The tuple. * @param tuple_colid The zero-based column index of the tuple from which the datum is to be read. - * @param arg_pos The ordinal position of the enveloping expression. * @return The datum read from the tuple. */ -Datum readTupleDatum(TupleTableSlot* slot, int tuple_colid, int arg_pos); +Datum readTupleDatum(TupleTableSlot* slot, int tuple_colid); /** * @brief Writes datum into a tuple. * @param slot The tuple. * @param tuple_colid The zero-based column index of the tuple into which the datum is to be written. * @param datum The datum to be written into the tuple. + * @param isnull Specifies whether the datum is null. */ -void writeTupleDatum(TupleTableSlot* slot, int tuple_colid, Datum datum); +void writeTupleDatum(TupleTableSlot* slot, int tuple_colid, Datum datum, int isnull); /** * @brief Reads the datum result of a sub-query. @@ -565,6 +579,187 @@ MOT::Key* GetSubQuerySearchKey(int subQueryIndex); /** @brief Retrieves the end-iterator key of a sub-query (LLVM only). */ MOT::Key* GetSubQueryEndIteratorKey(int subQueryIndex); + +/*--------------------------- LLVM try/catch Helpers for stored procedures ---------------------------*/ + +/** + * @brief Called when entering a try-catch block. + * @return Zero when processing the try-block, a positive value when processing a throws exception, and negative value + * if an error occurred. + */ +int8_t* LlvmPushExceptionFrame(); + +/** @brief Retrieves the jump buffer to the deepest exception frame. */ +int8_t* LlvmGetCurrentExceptionFrame(); + +/** @brief Called when leaving a try-catch block. */ +int LlvmPopExceptionFrame(); + +/** @brief Throws an exception value. */ +void LlvmThrowException(int exceptionValue); + +/** + * @brief Re-throws the recently thrown exception value (only in catch block). + */ +void LlvmRethrowException(); + +/** @brief Retrieves the recently thrown exception value (only in catch block). */ +int LlvmGetExceptionValue(); + +/** @brief Retrieves the recently thrown exception status (only in catch block). */ +int LlvmGetExceptionStatus(); + +/** @brief Sets the recently thrown exception status as handled (only in try/catch block). */ +void LlvmResetExceptionStatus(); + +/** @brief Sets the recently thrown exception value to zero. */ +void LlvmResetExceptionValue(); + +/** @brief Called when a try-catch block did not handle the current exception. */ +void LlvmUnwindExceptionFrame(); + +/** @brief Cleans up exception stack due to normal return. */ +void LlvmClearExceptionStack(); + +/** @brief Helper for debugging. */ +void LLvmPrintFrame(const char* msg, int8_t* frame); + +/*--------------------------- Stored Procedure Helpers ---------------------------*/ +/** @brief Abort executed stored procedure with this error code. . */ +void JitAbortFunction(int errorCode); + +/** @brief Queries whether any of the given parameters is null. */ +int JitHasNullParam(ParamListInfo params); + +/** @brief Queries the number of parameters stored in the given parameter list. */ +int JitGetParamCount(ParamListInfo params); + +/** @brief Retrieves the parameter at the specified position from the given parameter list. */ +Datum JitGetParamAt(ParamListInfo params, int paramId); + +/** @brief Queries whether the parameter in the given index is null. */ +int JitIsParamNull(ParamListInfo params, int paramId); + +/** @brief Retrieves the parameter at the specified position from the given parameter list. */ +Datum* JitGetParamAtRef(ParamListInfo params, int paramId); + +/** @brief Queries whether the parameter in the given index is null. */ +bool* JitIsParamNullRef(ParamListInfo params, int paramId); + +/** @brief Gets the parameter list associated with the current JIT context used for invoking a stored procedure. */ +ParamListInfo GetInvokeParamListInfo(); + +/** @brief Destroys a parameter array. */ +void DestroyParamListInfo(ParamListInfo params); + +/** @brief Sets a parameter value in the given parameter list. */ +void SetParamValue(ParamListInfo params, int paramId, int paramType, Datum datum, int isNull); + +/** @brief Sets a parameter value in the given stored procedure sub-query. */ +void SetSPSubQueryParamValue(int subQueryId, int id, int type, Datum value, int isNull); + +/** @brief Invoke the stored procedure referred by the current invoke context. */ +int InvokeStoredProcedure(); + +/** @brief Queries whether the given slot contains a composite result tuple. */ +int IsCompositeResult(TupleTableSlot* slot); + +/** @brief Create datum array for result heap tuple (composite return value). */ +Datum* CreateResultDatums(); + +/** @brief Create null array for result heap tuple (composite return value). */ +bool* CreateResultNulls(); + +/** @brief Set result value for result heap tuple (composite return value). */ +void SetResultValue(Datum* datums, bool* nulls, int index, Datum value, int isNull); + +/** @brief Creates a result heap tuple for the currently executing jitted stored procedure. */ +Datum CreateResultHeapTuple(Datum* dvalues, bool* nulls); + +/** @brief Sets a datum value in a result slot at the given index. */ +void SetSlotValue(TupleTableSlot* slot, int tupleColId, Datum value, int isNull); + +/** @brief Retrieves the datum value in a result slot at the given index. */ +Datum GetSlotValue(TupleTableSlot* slot, int tupleColId); + +/** @brief Retrieves the datum is-null property in a result slot at the given index. */ +int GetSlotIsNull(TupleTableSlot* slot, int tupleColId); + +/** @brief Retrieves the current sub-transaction id. */ +SubTransactionId JitGetCurrentSubTransactionId(); + +/** @brief Releases all JIT SP active and open sub-transactions. */ +void JitReleaseAllSubTransactions(bool rollback, SubTransactionId untilSubXid); + +/** @brief Execute required code at beginning of statement block with exceptions. */ +void JitBeginBlockWithExceptions(); + +/** @brief Execute required code at end of statement block with exceptions. */ +void JitEndBlockWithExceptions(); + +/** @brief Execute required code exception handler. */ +void JitCleanupBlockAfterException(); + +/** @brief Sets the current exception origin. */ +void JitSetExceptionOrigin(int origin); + +/** @brief Retrieves the current exception origin. */ +int JitGetExceptionOrigin(); + +/** @brief Retrieves reference to the current exception origin variable. */ +int* JitGetExceptionOriginRef(); + +/** @brief Do all clean-up required before returning from jitted SP. */ +void JitCleanupBeforeReturn(SubTransactionId initSubTxnId); + +/** @brief Executes a sub-query invoked from a stored procedure. */ +int JitExecSubQuery(int subQueryId, int tcount); + +/** @brief Release resources used during execution of non-jittable sub-query. */ +void JitReleaseNonJitSubQueryResources(int subQueryId); + +/** @brief Retrieves the number of tuples processes by the last sub-query invoked from a stored procedure. */ +int JitGetTuplesProcessed(int subQueryId); + +/** @brief Retrieves datum value from the result tuple of the last sub-query invoked from a stored procedure. */ +Datum JitGetSubQuerySlotValue(int subQueryId, int tupleColId); + +/** @brief Retrieves datum is-null from the result tuple of the last sub-query invoked from a stored procedure. */ +int JitGetSubQuerySlotIsNull(int subQueryId, int tupleColId); + +/** @brief Retrieves the result heap tuple associated with the composite sub-query. */ +HeapTuple JitGetSubQueryResultHeapTuple(int subQueryId); + +/** @brief Retrieves the datum at the given position in the heap tuple. */ +Datum JitGetHeapTupleValue(HeapTuple tuple, int subQueryId, int columnId, int* isNull); + +/** @brief Get the execution result of a non-jittable SP query. */ +int JitGetSpiResult(int subQueryId); + +/** @brief Converts one datum type to another via string. */ +Datum JitConvertViaString(Datum value, Oid resultType, Oid targetType, int typeMod); + +/** @brief Cast datum value. */ +Datum JitCastValue(Datum value, Oid sourceType, Oid targetType, int typeMod, int coercePath, Oid funcId, + int coercePath2, Oid funcId2, int nargs, int typeByVal); + +/** @brief Saves error information of called function without exception block. */ +void JitSaveErrorInfo(Datum errorMessage, int sqlState, Datum sqlStateString); + +/** @brief Retrieves the error message associated with the last SQL error. */ +Datum JitGetErrorMessage(); + +/** @brief Retrieves the SQL state associated with the last SQL error. */ +int JitGetSqlState(); + +/** @brief Retrieves the SQL state (string-form) associated with the last SQL error. */ +Datum JitGetSqlStateString(); + +/** @brief Converts int value to inverse Boolean datum value. */ +Datum JitGetDatumIsNotNull(int isNull); + +/** @brief Emit profile data. */ +void EmitProfileData(uint32_t functionId, uint32_t regionId, int beginRegion); } // extern "C" #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm.cpp b/src/gausskernel/storage/mot/jit_exec/jit_llvm.cpp index cb7482926..d12c72634 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm.cpp @@ -57,33 +57,39 @@ bool JitCanInitThreadCodeGen() MOT_LOG_WARN("LLVM default target triple is: %s, process triple is: %s", llvm::sys::getDefaultTargetTriple().c_str(), llvm::sys::getProcessTriple().c_str()); + MOT_LOG_WARN("LLVM is disabled due to undetected platform"); return false; } #ifdef __aarch64__ return true; // Using native llvm on ARM #else - bool canInit = false; - if (IS_PGXC_DATANODE && IsMotCodegenEnabled()) { - canInit = GlobalCodeGenEnvironmentSuccess; - if (!canInit) { - MOT_LOG_WARN("LLVM environment is not initialized, native LLVM will not be used."); - } + if (!IsMotCodegenEnabled()) { + MOT_LOG_INFO("LLVM is disabled by configuration"); + return false; } - return canInit; + if (!IS_PGXC_DATANODE) { + MOT_LOG_INFO("LLVM is disabled: current node is not a data node"); + return false; + } + if (!GlobalCodeGenEnvironmentSuccess) { + MOT_LOG_WARN("LLVM is disabled due to LLVM environment initialization failure"); + return false; + } + return true; #endif } /** @brief Prepares for code generation. */ GsCodeGen* SetupCodegenEnv() { - MOT_ASSERT(g_instance.mot_cxt.jitExecMode == JIT_EXEC_MODE_LLVM); - // create GsCodeGen object for LLVM code generation GsCodeGen* code_gen = New(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)) GsCodeGen(); if (code_gen != nullptr) { code_gen->initialize(); code_gen->createNewModule(); + // we use large compilation model for ARM machines (otherwise relocation fails when generated code is large) + code_gen->getCurrentModule()->setCodeModel(llvm::CodeModel::Large); } return code_gen; } diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm.h index b9e448a75..065e14926 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm.h @@ -54,13 +54,27 @@ namespace JitExec { * @return Zero if succeeded, otherwise an error code. * @note This function may cause transaction abort. */ -typedef int (*JitFunc)(MOT::Table* table, MOT::Index* index, MOT::Key* key, MOT::BitmapSet* bitmapSet, +typedef int (*JitQueryFunc)(MOT::Table* table, MOT::Index* index, MOT::Key* key, MOT::BitmapSet* bitmapSet, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded, int newScan, MOT::Key* endIteratorKey, MOT::Table* innerTable, MOT::Index* innerIndex, MOT::Key* innerKey, MOT::Key* innerEndIteratorKey); // the number of arguments in the jitted function -#define MOT_JIT_FUNC_ARG_COUNT 14 +#define MOT_JIT_QUERY_ARG_COUNT 14 + +/** + * @typedef Jitted stored procedure function prototype. + * @param params The list of bound parameters passed to the query. + * @param[out] slot The slot used for reporting select result. + * @param[out] tuplesProcessed The variable used to report the number of processed rows. + * @param[out] scanEnded Signifies in range scans whether scan ended. + * @return Zero if succeeded, otherwise an error code. + * @note This function may cause transaction abort. + */ +typedef int (*JitSPFunc)(ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded); + +// the number of arguments in the jitted function +#define MOT_JIT_FUNC_ARG_COUNT 4 /** @brief Prints startup information regarding LLVM version. */ void PrintNativeLlvmStartupInfo(); diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.cpp b/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.cpp index b3e91d387..08717297e 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.cpp @@ -32,31 +32,28 @@ #include "jit_util.h" #include "mot_error.h" #include "utilities.h" +#include "jit_source_map.h" +#include "storage/mot/jit_def.h" #include "catalog/pg_aggregate.h" +#define JIT_PROFILE_CMD(ctx, begin) InjectProfileData(ctx, GetActiveNamespace(), (ctx)->m_queryString, begin) +#define JIT_PROFILE_BEGIN(ctx) JIT_PROFILE_CMD(ctx, true) +#define JIT_PROFILE_END(ctx) JIT_PROFILE_CMD(ctx, false) + using namespace dorado; namespace JitExec { DECLARE_LOGGER(JitLlvmBlocks, JitExec) -static bool ProcessJoinOpExpr( - JitLlvmCodeGenContext* ctx, const OpExpr* op_expr, int* column_count, int* column_array, int* max_arg); -static bool ProcessJoinBoolExpr( - JitLlvmCodeGenContext* ctx, const BoolExpr* boolexpr, int* column_count, int* column_array, int* max_arg); -static llvm::Value* ProcessFilterExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFilter* filter, int* max_arg); -static llvm::Value* ProcessExpr( - JitLlvmCodeGenContext* ctx, Expr* expr, int& result_type, int arg_pos, int depth, int* max_arg); -static llvm::Value* ProcessExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitExpr* expr, int* max_arg); - /*--------------------------- Helpers to generate compound LLVM code ---------------------------*/ /** @brief Creates a jitted function for code generation. Builds prototype and entry block. */ void CreateJittedFunction(JitLlvmCodeGenContext* ctx, const char* function_name) { - llvm::Value* llvmargs[MOT_JIT_FUNC_ARG_COUNT]; + llvm::Value* llvmargs[MOT_JIT_QUERY_ARG_COUNT]; // define the function prototype - GsCodeGen::FnPrototype fn_prototype(ctx->_code_gen, function_name, ctx->INT32_T); + GsCodeGen::FnPrototype fn_prototype(ctx->m_codeGen, function_name, ctx->INT32_T); fn_prototype.addArgument(GsCodeGen::NamedVariable("table", ctx->TableType->getPointerTo())); fn_prototype.addArgument(GsCodeGen::NamedVariable("index", ctx->IndexType->getPointerTo())); fn_prototype.addArgument(GsCodeGen::NamedVariable("key", ctx->KeyType->getPointerTo())); @@ -72,7 +69,7 @@ void CreateJittedFunction(JitLlvmCodeGenContext* ctx, const char* function_name) fn_prototype.addArgument(GsCodeGen::NamedVariable("inner_key", ctx->KeyType->getPointerTo())); fn_prototype.addArgument(GsCodeGen::NamedVariable("inner_end_iterator_key", ctx->KeyType->getPointerTo())); - ctx->m_jittedQuery = fn_prototype.generatePrototype(ctx->_builder, &llvmargs[0]); + ctx->m_jittedFunction = fn_prototype.generatePrototype(ctx->m_builder, &llvmargs[0]); // get the arguments int arg_index = 0; @@ -91,6 +88,8 @@ void CreateJittedFunction(JitLlvmCodeGenContext* ctx, const char* function_name) ctx->inner_key_value = llvmargs[arg_index++]; ctx->inner_end_iterator_key_value = llvmargs[arg_index++]; + ctx->rows_processed = ctx->m_builder->CreateAlloca(ctx->INT64_T, nullptr, "rowsProcessed"); + for (uint32_t i = 0; i < ctx->m_subQueryCount; ++i) { ctx->m_subQueryData[i].m_slot = AddGetSubQuerySlot(ctx, i); ctx->m_subQueryData[i].m_table = AddGetSubQueryTable(ctx, i); @@ -100,19 +99,26 @@ void CreateJittedFunction(JitLlvmCodeGenContext* ctx, const char* function_name) } IssueDebugLog("Starting execution of jitted function"); + + JIT_PROFILE_BEGIN(ctx); } /** @brief Builds a code segment for checking if soft memory limit has been reached. */ void buildIsSoftMemoryLimitReached(JitLlvmCodeGenContext* ctx) { - JIT_IF_BEGIN(soft_limit_reached) + JIT_IF_BEGIN(soft_limit_reached); llvm::Value* is_limit_reached_res = AddIsSoftMemoryLimitReached(ctx); - JIT_IF_EVAL(is_limit_reached_res) - IssueDebugLog("Soft memory limit reached"); - JIT_RETURN_CONST(MOT::RC_MEMORY_ALLOCATION_ERROR); - JIT_ELSE() - IssueDebugLog("Soft memory limit not reached"); - JIT_IF_END() + JIT_IF_EVAL(is_limit_reached_res); + { + IssueDebugLog("Soft memory limit reached"); + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_MEMORY_ALLOCATION_ERROR)); + } + JIT_ELSE(); + { + IssueDebugLog("Soft memory limit not reached"); + } + JIT_IF_END(); } /** @brief Builds a code segment for writing datum value to a column. */ @@ -122,20 +128,25 @@ static void buildWriteDatumColumn(JitLlvmCodeGenContext* ctx, llvm::Value* row, IssueDebugLog("Set null bit"); if (ctx->_table_info.m_table->GetField(colid)->m_isNotNull) { - JIT_IF_BEGIN(check_null_violation) - JIT_IF_EVAL_CMP(set_null_bit_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Null constraint violated"); - JIT_RETURN(set_null_bit_res); - JIT_IF_END() + JIT_IF_BEGIN(check_null_violation); + JIT_IF_EVAL_CMP(set_null_bit_res, JIT_CONST_INT32(MOT::RC_OK), JIT_ICMP_NE); + { + IssueDebugLog("Null constraint violated"); + JIT_PROFILE_END(ctx); + JIT_RETURN(set_null_bit_res); + } + JIT_IF_END(); } // now check if the result is not null, and if so write column datum - llvm::Value* is_expr_null = AddGetExprArgIsNull(ctx, 0); - JIT_IF_BEGIN(check_expr_null) - JIT_IF_EVAL_NOT(is_expr_null) - IssueDebugLog("Encountered non-null expression result, writing datum column"); - AddWriteDatumColumn(ctx, colid, row, datum_value); - JIT_IF_END() + llvm::Value* is_expr_null = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(check_expr_null); + JIT_IF_EVAL_NOT(is_expr_null); + { + IssueDebugLog("Encountered non-null expression result, writing datum column"); + AddWriteDatumColumn(ctx, colid, row, datum_value); + } + JIT_IF_END(); } /** @brief Builds a code segment for writing a row. */ @@ -144,137 +155,32 @@ void buildWriteRow(JitLlvmCodeGenContext* ctx, llvm::Value* row, bool isPKey, Ji IssueDebugLog("Writing row"); llvm::Value* write_row_res = AddWriteRow(ctx, row); - JIT_IF_BEGIN(check_row_written) - JIT_IF_EVAL_CMP(write_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not written"); - // need to emit cleanup code - if (!isPKey) { - AddDestroyCursor(ctx, cursor); - } - JIT_RETURN(write_row_res); - JIT_IF_END() -} - -/** @brief Process a join expression (WHERE clause) and generate code to build a search key. */ -static bool ProcessJoinExpr(JitLlvmCodeGenContext* ctx, Expr* expr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - if (expr->type == T_OpExpr) { - result = ProcessJoinOpExpr(ctx, (OpExpr*)expr, column_count, column_array, max_arg); - } else if (expr->type == T_BoolExpr) { - result = ProcessJoinBoolExpr(ctx, (BoolExpr*)expr, column_count, column_array, max_arg); - } else { - MOT_LOG_TRACE("Unsupported expression type %d while processing Join Expr", (int)expr->type); - } - return result; -} - -/** @brief Process an operator expression (process only "COLUMN equals EXPR" operators). */ -static bool ProcessJoinOpExpr( - JitLlvmCodeGenContext* ctx, const OpExpr* op_expr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - // process only point queries - if (IsWhereOperatorSupported(op_expr->opno)) { - llvm::Value* value = nullptr; - ListCell* lc1 = nullptr; - int colid = -1; - int vartype = -1; - int result_type = -1; - - foreach (lc1, op_expr->args) { - Expr* expr = (Expr*)lfirst(lc1); - // sometimes relabel expression hides the inner expression, so we peel it off - if (expr->type == T_RelabelType) { - expr = ((RelabelType*)expr)->arg; - } - if (expr->type == T_Var) { - Var* var = (Var*)expr; - colid = var->varattno; - vartype = var->vartype; - if (!IsTypeSupported(vartype)) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported type %d", vartype); - return false; - } - // no further processing - } else { - value = ProcessExpr(ctx, expr, result_type, 0, 0, max_arg); - if (value == nullptr) { - MOT_LOG_TRACE("Unsupported operand type %d while processing Join OpExpr", (int)expr->type); - } - if (!IsTypeSupported(result_type)) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported result type %d", result_type); - return false; - } - break; - } + JIT_IF_BEGIN(check_row_written); + JIT_IF_EVAL_CMP(write_row_res, JIT_CONST_INT32(MOT::RC_OK), JIT_ICMP_NE); + { + IssueDebugLog("Row not written"); + // need to emit cleanup code + if (!isPKey) { + AddDestroyCursor(ctx, cursor); } - - if ((colid != -1) && (value != nullptr) && (vartype != -1) && (result_type != -1)) { - if (result_type != vartype) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): vartype %d and result-type %d mismatch", vartype, result_type); - return false; - } - // execute: column = getColumnAt(colid) - llvm::Value* column = AddGetColumnAt(ctx, - colid, - JIT_RANGE_SCAN_MAIN); // no need to translate to zero-based index (first column is null bits) - int index_colid = ctx->_table_info.m_columnMap[colid]; - AddBuildDatumKey(ctx, column, index_colid, value, vartype, JIT_RANGE_ITERATOR_START, JIT_RANGE_SCAN_MAIN); - - MOT_LOG_DEBUG("Encountered table column %d, index column %d in where clause", colid, index_colid); - ++(*column_count); - column_array[index_colid] = 1; - result = true; - } else { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Invalid expression (colid=%d, value=%p, vartype=%d, result_type=%d)", - colid, - value, - vartype, - result_type); - } - } else { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported operator type %u", op_expr->opno); + JIT_PROFILE_END(ctx); + JIT_RETURN(write_row_res); } - - return result; -} - -/** @brief Process a boolean operator (process only AND operators, since we handle only point queries, or full-prefix - * range update). */ -static bool ProcessJoinBoolExpr( - JitLlvmCodeGenContext* ctx, const BoolExpr* boolexpr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - if (boolexpr->boolop == AND_EXPR) { - // now traverse args to get param index to build search key - ListCell* lc = nullptr; - foreach (lc, boolexpr->args) { - // each element is Expr - Expr* expr = (Expr*)lfirst(lc); - result = ProcessJoinExpr(ctx, expr, column_count, column_array, max_arg); - if (!result) { - MOT_LOG_TRACE("Failed to process operand while processing Join BoolExpr"); - break; - } - } - } else { - MOT_LOG_TRACE("Unsupported bool operation %d while processing Join BoolExpr", (int)boolexpr->boolop); - } - return result; + JIT_IF_END(); } /** @brief Adds code to reset the number of rows processed. */ void buildResetRowsProcessed(JitLlvmCodeGenContext* ctx) { - ctx->rows_processed = llvm::ConstantInt::get(ctx->INT64_T, 0, true); + ctx->m_builder->CreateStore(JIT_CONST_INT64(0), ctx->rows_processed, true); } /** @brief Adds code to increment the number of rows processed. */ void buildIncrementRowsProcessed(JitLlvmCodeGenContext* ctx) { - llvm::ConstantInt* one_value = llvm::ConstantInt::get(ctx->INT64_T, 1, true); - ctx->rows_processed = ctx->_builder->CreateAdd(ctx->rows_processed, one_value); + llvm::Value* rowsProcessed = ctx->m_builder->CreateLoad(ctx->rows_processed, true); + llvm::Value* newValue = ctx->m_builder->CreateAdd(rowsProcessed, JIT_CONST_INT64(1)); + ctx->m_builder->CreateStore(newValue, ctx->rows_processed, true); } /** @brief Adds code to create a new row. */ @@ -282,11 +188,14 @@ llvm::Value* buildCreateNewRow(JitLlvmCodeGenContext* ctx) { llvm::Value* row = AddCreateNewRow(ctx); - JIT_IF_BEGIN(check_row_created) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Failed to create row"); - JIT_RETURN_CONST(MOT::RC_MEMORY_ALLOCATION_ERROR); - JIT_IF_END() + JIT_IF_BEGIN(check_row_created); + JIT_IF_EVAL_NOT(row); + { + IssueDebugLog("Failed to create row"); + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_MEMORY_ALLOCATION_ERROR)); + } + JIT_IF_END(); return row; } @@ -298,57 +207,49 @@ llvm::Value* buildSearchRow(JitLlvmCodeGenContext* ctx, MOT::AccessType access_t IssueDebugLog("Searching row"); llvm::Value* row = AddSearchRow(ctx, access_type, range_scan_type, subQueryIndex); - JIT_IF_BEGIN(check_row_found) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Row not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() + JIT_IF_BEGIN(check_row_found); + JIT_IF_EVAL_NOT(row); + { + IssueDebugLog("Row not found"); + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } + JIT_IF_END(); IssueDebugLog("Row found"); return row; } -static llvm::Value* buildFilter(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFilter* filter, int* max_arg) -{ - llvm::Value* result = nullptr; - llvm::Value* lhs_expr = ProcessExpr(ctx, row, filter->_lhs_operand, max_arg); - if (lhs_expr == nullptr) { - MOT_LOG_TRACE( - "buildFilter(): Failed to process LHS expression with type %d", (int)filter->_lhs_operand->_expr_type); - } else { - llvm::Value* rhs_expr = ProcessExpr(ctx, row, filter->_rhs_operand, max_arg); - if (rhs_expr == nullptr) { - MOT_LOG_TRACE( - "buildFilter(): Failed to process RHS expression with type %d", (int)filter->_rhs_operand->_expr_type); - } else { - result = ProcessFilterExpr(ctx, row, filter, max_arg); - } - } - return result; -} - -bool buildFilterRow( - JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFilterArray* filters, int* max_arg, llvm::BasicBlock* next_block) +bool buildFilterRow(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitFilterArray* filters, + llvm::BasicBlock* next_block) { // 1. for each filter expression we generate the equivalent instructions which should evaluate to true or false // 2. We assume that all filters are applied with AND operator between them (imposed during query analysis/plan // phase) for (int i = 0; i < filters->_filter_count; ++i) { - llvm::Value* filter_expr = buildFilter(ctx, row, &filters->_scan_filters[i], max_arg); - if (filter_expr == nullptr) { + llvm::Value* filterExpr = ProcessExpr(ctx, row, innerRow, filters->_scan_filters[i]); + if (filterExpr == nullptr) { MOT_LOG_TRACE("buildFilterRow(): Failed to process filter expression %d", i); return false; } - JIT_IF_BEGIN(filter_row) - JIT_IF_EVAL_NOT(filter_expr) - IssueDebugLog("Row filter expression failed"); - if (next_block == nullptr) { - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - } else { - ctx->_builder->CreateBr(next_block); + JIT_IF_BEGIN(filter_row); + IssueDebugLog("Checking if row passes filter"); + JIT_IF_EVAL_NOT(filterExpr); + { + IssueDebugLog("Row did not pass filter"); + if (next_block == nullptr) { + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } else { + ctx->m_builder->CreateBr(next_block); + } } - JIT_IF_END() + JIT_ELSE(); + { + IssueDebugLog("Row passed filter"); + } + JIT_IF_END(); } return true; } @@ -359,11 +260,14 @@ void buildInsertRow(JitLlvmCodeGenContext* ctx, llvm::Value* row) IssueDebugLog("Inserting row"); llvm::Value* insert_row_res = AddInsertRow(ctx, row); - JIT_IF_BEGIN(check_row_inserted) - JIT_IF_EVAL_CMP(insert_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not inserted"); - JIT_RETURN(insert_row_res); - JIT_IF_END() + JIT_IF_BEGIN(check_row_inserted); + JIT_IF_EVAL_CMP(insert_row_res, JIT_CONST_INT32(MOT::RC_OK), JIT_ICMP_NE); + { + IssueDebugLog("Row not inserted"); + JIT_PROFILE_END(ctx); + JIT_RETURN(insert_row_res); + } + JIT_IF_END(); IssueDebugLog("Row inserted"); } @@ -374,11 +278,14 @@ void buildDeleteRow(JitLlvmCodeGenContext* ctx) IssueDebugLog("Deleting row"); llvm::Value* delete_row_res = AddDeleteRow(ctx); - JIT_IF_BEGIN(check_delete_row) - JIT_IF_EVAL_CMP(delete_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not deleted"); - JIT_RETURN(delete_row_res); - JIT_IF_END() + JIT_IF_BEGIN(check_delete_row); + JIT_IF_EVAL_CMP(delete_row_res, JIT_CONST_INT32(MOT::RC_OK), JIT_ICMP_NE); + { + IssueDebugLog("Row not deleted"); + JIT_PROFILE_END(ctx); + JIT_RETURN(delete_row_res); + } + JIT_IF_END(); IssueDebugLog("Row deleted"); } @@ -391,11 +298,14 @@ static llvm::Value* buildSearchIterator(JitLlvmCodeGenContext* ctx, JitIndexScan IssueDebugLog("Searching range start"); llvm::Value* itr = AddSearchIterator(ctx, index_scan_direction, range_bound_mode, range_scan_type, subQueryIndex); - JIT_IF_BEGIN(check_itr_found) - JIT_IF_EVAL_NOT(itr) - IssueDebugLog("Range start not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() + JIT_IF_BEGIN(check_itr_found); + JIT_IF_EVAL_NOT(itr); + { + IssueDebugLog("Range start not found"); + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } + JIT_IF_END(); IssueDebugLog("Range start found"); return itr; @@ -409,565 +319,508 @@ static llvm::Value* buildBeginIterator( IssueDebugLog("Getting begin iterator for full-scan"); llvm::Value* itr = AddBeginIterator(ctx, rangeScanType, subQueryIndex); - JIT_IF_BEGIN(check_itr_found) - JIT_IF_EVAL_NOT(itr) - IssueDebugLog("Begin iterator not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() + JIT_IF_BEGIN(check_itr_found); + JIT_IF_EVAL_NOT(itr); + { + IssueDebugLog("Begin iterator not found"); + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } + JIT_IF_END(); IssueDebugLog("Range start found"); return itr; } /** @brief Adds code to get row from iterator. */ -llvm::Value* buildGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* endLoopBlock, - MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, JitLlvmRuntimeCursor* cursor, - JitRangeScanType range_scan_type, int subQueryIndex /* = -1 */) +void BuildCheckRowExistsInIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* endLoopBlock, + JitIndexScanDirection indexScanDirection, JitLlvmRuntimeCursor* cursor, JitRangeScanType rangeScanType, + int subQueryIndex /* = -1 */) +{ + IssueDebugLog("Checking if row exists in iterator"); + llvm::Value* rowExists = AddCheckRowExistsInIterator(ctx, indexScanDirection, cursor, rangeScanType, subQueryIndex); + + JIT_IF_BEGIN(check_itr_row_found); + JIT_IF_EVAL_NOT(rowExists); + { + IssueDebugLog("Row not found in in iterator"); + JIT_GOTO(endLoopBlock); + // NOTE: we can actually do here a JIT_WHILE_BREAK() if we have an enclosing while loop + } + JIT_IF_END(); + + IssueDebugLog("Row found in iterator"); +} + +/** @brief Adds code to get row from iterator. */ +llvm::Value* buildGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* startLoopBlock, + llvm::BasicBlock* endLoopBlock, MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, + JitLlvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, int subQueryIndex /* = -1 */) { IssueDebugLog("Retrieving row from iterator"); llvm::Value* row = AddGetRowFromIterator(ctx, access_mode, index_scan_direction, cursor, range_scan_type, subQueryIndex); - JIT_IF_BEGIN(check_itr_row_found) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Iterator row not found"); - JIT_GOTO(endLoopBlock); - // NOTE: we can actually do here a JIT_WHILE_BREAK() if we have an enclosing while loop - JIT_IF_END() + JIT_IF_BEGIN(check_itr_row_found); + JIT_IF_EVAL_NOT(row); + { + IssueDebugLog("Iterator row not found"); + JIT_GOTO(endLoopBlock); + // NOTE: we can actually do here a JIT_WHILE_BREAK() if we have an enclosing while loop + } + JIT_IF_END(); IssueDebugLog("Iterator row found"); return row; } -/** @brief Process constant expression. */ -static llvm::Value* ProcessConstExpr( - JitLlvmCodeGenContext* ctx, const Const* const_value, int& result_type, int arg_pos, int depth, int* max_arg) +static llvm::Value* AddInvokePGFunction(JitLlvmCodeGenContext* ctx, Oid funcId, uint32_t argNum, Oid funcCollationId, + Oid resultCollationId, llvm::Value** args, llvm::Value** argIsNull, Oid* argTypes) { - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing CONST expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); + uint32_t argCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(funcId, &argCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("AddInvokePGFunction(): Cannot find function by id: %u", (unsigned)funcId); return nullptr; } + MOT_ASSERT(argCount == argNum); - // verify type is supported and return compile-time constant (no need to generate code for runtime evaluation) - if (IsTypeSupported(const_value->consttype)) { - result_type = const_value->consttype; - AddSetExprArgIsNull(ctx, arg_pos, const_value->constisnull); // mark expression null status - if (IsPrimitiveType(result_type)) { - result = llvm::ConstantInt::get(ctx->INT64_T, const_value->constvalue, true); + llvm::Value* result = nullptr; + if (argCount == 0) { + result = AddInvokePGFunction0(ctx, funcPtr, funcCollationId); + } else if (argCount == 1) { + result = AddInvokePGFunction1(ctx, funcPtr, funcCollationId, isStrict, args[0], argIsNull[0], argTypes[0]); + } else if (argCount == 2) { + result = AddInvokePGFunction2(ctx, + funcPtr, + funcCollationId, + isStrict, + args[0], + argIsNull[0], + argTypes[0], + args[1], + argIsNull[1], + argTypes[1]); + } else if (argCount == 3) { + result = AddInvokePGFunction3(ctx, + funcPtr, + funcCollationId, + isStrict, + args[0], + argIsNull[0], + argTypes[2], + args[1], + argIsNull[1], + argTypes[1], + args[2], + argIsNull[2], + argTypes[2]); + } else { + result = AddInvokePGFunctionN(ctx, funcPtr, funcCollationId, isStrict, args, argIsNull, argTypes, argCount); + } + AddSetExprCollation(ctx, resultCollationId); + return result; +} + +static llvm::Value* ProcessConstExpr(JitLlvmCodeGenContext* ctx, const JitConstExpr* expr) +{ + llvm::Value* result = nullptr; + if (IsTypeSupported(expr->_const_type)) { + AddSetExprIsNull(ctx, expr->_is_null); // mark expression null status + AddSetExprCollation(ctx, expr->m_collationId); + if (IsPrimitiveType(expr->_const_type)) { + result = JIT_CONST_INT64(expr->_value); } else { - int constId = AllocateConstId(ctx, result_type, const_value->constvalue, const_value->constisnull); + int constId = AllocateConstId(ctx, expr->_const_type, expr->_value, expr->_is_null); if (constId == -1) { MOT_LOG_TRACE("Failed to allocate constant identifier"); } else { - result = AddGetConstAt(ctx, constId, arg_pos); + result = AddGetConstAt(ctx, constId); } } - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } } else { - MOT_LOG_TRACE("Failed to process const expression: type %d unsupported", (int)result_type); + MOT_LOG_TRACE("Failed to process const expression: type %d unsupported", (int)expr->_const_type); } - MOT_LOG_DEBUG("%*s <-- Processing CONST expression result: %p", depth, "", result); return result; } -/** @brief Process Param expression. */ -static llvm::Value* ProcessParamExpr( - JitLlvmCodeGenContext* ctx, const Param* param, int& result_type, int arg_pos, int depth, int* max_arg) +static llvm::Value* ProcessParamExpr(JitLlvmCodeGenContext* ctx, const JitParamExpr* expr) { - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing PARAM expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - // verify type is supported and generate code to extract the parameter in runtime - if (IsTypeSupported(param->paramtype)) { - result_type = param->paramtype; - result = AddGetDatumParam(ctx, param->paramid - 1, arg_pos); - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } - } - - MOT_LOG_DEBUG("%*s <-- Processing PARAM expression result: %p", depth, "", result); + llvm::Value* result = AddGetDatumParam(ctx, expr->_param_id); + AddSetExprCollation(ctx, expr->m_collationId); return result; } -/** @brief Process Relabel expression as Param expression. */ -static llvm::Value* ProcessRelabelExpr( - JitLlvmCodeGenContext* ctx, RelabelType* relabel_type, int& result_type, int arg_pos, int depth, int* max_arg) -{ - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("Processing RELABEL expression"); - Expr* expr = (Expr*)relabel_type->arg; - if (expr->type == T_Param) { - Param* param = (Param*)expr; - result = ProcessParamExpr(ctx, param, result_type, arg_pos, depth, max_arg); - } else { - MOT_LOG_TRACE("Unexpected relabel argument type: %d", (int)expr->type); - } - return result; -} - -/** @brief Proess Var expression. */ static llvm::Value* ProcessVarExpr( - JitLlvmCodeGenContext* ctx, const Var* var, int& result_type, int arg_pos, int depth, int* max_arg) -{ - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing VAR expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - // verify that type is supported and generate code to read column datum during runtime - if (IsTypeSupported(var->vartype)) { - result_type = var->vartype; - int table_colid = var->varattno; - result = AddReadDatumColumn(ctx, ctx->table_value, nullptr, table_colid, arg_pos); - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } - } - - MOT_LOG_DEBUG("%*s <-- Processing VAR expression result: %p", depth, "", result); - return result; -} - -/** @brief Adds call to PG unary operator. */ -static llvm::Value* AddExecUnaryOperator( - JitLlvmCodeGenContext* ctx, llvm::Value* param, llvm::FunctionCallee unary_operator, int arg_pos) -{ - llvm::Constant* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall(ctx, unary_operator, param, arg_pos_value, nullptr); -} - -/** @brief Adds call to PG binary operator. */ -static llvm::Value* AddExecBinaryOperator(JitLlvmCodeGenContext* ctx, llvm::Value* lhs_param, llvm::Value* rhs_param, - llvm::FunctionCallee binary_operator, int arg_pos) -{ - llvm::Constant* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall(ctx, binary_operator, lhs_param, rhs_param, arg_pos_value, nullptr); -} - -/** @brief Adds call to PG ternary operator. */ -static llvm::Value* AddExecTernaryOperator(JitLlvmCodeGenContext* ctx, llvm::Value* param1, llvm::Value* param2, - llvm::Value* param3, llvm::FunctionCallee ternary_operator, int arg_pos) -{ - llvm::Constant* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall(ctx, ternary_operator, param1, param2, param3, arg_pos_value, nullptr); -} - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecUnaryOperator(ctx, args[0], ctx->_builtin_##name, arg_pos); \ - break; - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecBinaryOperator(ctx, args[0], args[1], ctx->_builtin_##name, arg_pos); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecTernaryOperator(ctx, args[0], args[1], args[2], ctx->_builtin_##name, arg_pos); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) - -/** @brief Process operator expression. */ -static llvm::Value* ProcessOpExpr( - JitLlvmCodeGenContext* ctx, const OpExpr* op_expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing OP %u expression", depth, "", op_expr->opfuncid); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (list_length(op_expr->args) > 3) { - MOT_LOG_TRACE("Unsupported operator %u: too many arguments", op_expr->opno); - return nullptr; - } - - llvm::Value* args[3] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - int dummy = 0; - - // process operator arguments (each one is an expression by itself) - ListCell* lc = nullptr; - foreach (lc, op_expr->args) { - Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = ProcessExpr(ctx, sub_expr, dummy, arg_pos + arg_num, depth + 1, max_arg); - if (args[arg_num] == nullptr) { - MOT_LOG_TRACE("Failed to process operator sub-expression %d", arg_num); - return nullptr; - } - if (++arg_num == 3) { - break; - } - } - - // process the operator - generate code to call the operator in runtime - result_type = op_expr->opresulttype; - switch (op_expr->opfuncid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported operator function type: %u", op_expr->opfuncid); - break; - } - - MOT_LOG_DEBUG("%*s <-- Processing OP %u expression result: %p", depth, "", op_expr->opfuncid, result); - return result; -} - -/** @brief Process function expression. */ -static llvm::Value* ProcessFuncExpr( - JitLlvmCodeGenContext* ctx, const FuncExpr* func_expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing FUNC %d expression", depth, "", (int)func_expr->funcid); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (list_length(func_expr->args) > 3) { - MOT_LOG_TRACE("Unsupported function %d: too many arguments", func_expr->funcid); - return nullptr; - } - - llvm::Value* args[3] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - int dummy = 0; - - // process function arguments (each one is an expression by itself) - ListCell* lc = nullptr; - foreach (lc, func_expr->args) { - Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = ProcessExpr(ctx, sub_expr, dummy, arg_pos + arg_num, depth + 1, max_arg); - if (args[arg_num] == nullptr) { - MOT_LOG_TRACE("Failed to process function sub-expression %d", arg_num); - return nullptr; - } - if (++arg_num == 3) { - break; - } - } - - // process the function - generate code to call the function in runtime - result_type = func_expr->funcresulttype; - switch (func_expr->funcid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported function type: %d", (int)func_expr->funcid); - break; - } - - MOT_LOG_DEBUG("%*s <-- Processing FUNC %d expression result: %p", depth, "", (int)func_expr->funcid, result); - return result; -} - -// we allow only binary operators for filters -#undef APPLY_UNARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to unary builtin: " #name); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to ternary builtin: " #name); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to unary cast builtin: " #name); \ - break; - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to ternary cast builtin: " #name); \ - break; - -static llvm::Value* ProcessFilterExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFilter* filter, int* max_arg) + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, const JitVarExpr* expr) { llvm::Value* result = nullptr; - llvm::Value* args[3] = {nullptr, nullptr, nullptr}; - - args[0] = ProcessExpr(ctx, row, filter->_lhs_operand, max_arg); - if (!args[0]) { - MOT_LOG_TRACE("Failed to process filter LHS expression"); - return nullptr; - } - - args[1] = ProcessExpr(ctx, row, filter->_rhs_operand, max_arg); - if (!args[1]) { - MOT_LOG_TRACE("Failed to process filter RHS expression"); - return nullptr; - } - - int arg_pos = 0; // always a top-level expression - switch (filter->_filter_op_funcid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported filter function type: %d", filter->_filter_op_funcid); - break; - } - - return result; -} - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -/** @brief Process an expression. Generates code to evaluate the expression. */ -static llvm::Value* ProcessExpr( - JitLlvmCodeGenContext* ctx, Expr* expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - llvm::Value* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing expression %d", depth, "", (int)expr->type); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - // case 1: assign from parameter (cases like: s_quantity = $1) - if (expr->type == T_Const) { - result = ProcessConstExpr(ctx, (Const*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_Param) { - result = ProcessParamExpr(ctx, (Param*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_RelabelType) { - result = ProcessRelabelExpr(ctx, (RelabelType*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_Var) { - result = ProcessVarExpr(ctx, (Var*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_OpExpr) { - result = ProcessOpExpr(ctx, (OpExpr*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_FuncExpr) { - result = ProcessFuncExpr(ctx, (FuncExpr*)expr, result_type, arg_pos, depth + 1, max_arg); - } else { - MOT_LOG_TRACE( - "Failed to generate jitted code for query: unsupported target expression type: %d", (int)expr->type); - } - - MOT_LOG_DEBUG("%*s <-- Processing expression %d result: %p", depth, "", (int)expr->type, result); - return result; -} - -static llvm::Value* ProcessConstExpr(JitLlvmCodeGenContext* ctx, const JitConstExpr* expr, int* max_arg) -{ - llvm::Value* result = nullptr; - AddSetExprArgIsNull(ctx, expr->_arg_pos, expr->_is_null); // mark expression null status - if (IsPrimitiveType(expr->_const_type)) { - result = llvm::ConstantInt::get(ctx->INT64_T, expr->_value, true); - } else { - int constId = AllocateConstId(ctx, expr->_const_type, expr->_value, expr->_is_null); - if (constId == -1) { - MOT_LOG_TRACE("Failed to allocate constant identifier"); - } else { - result = AddGetConstAt(ctx, constId, expr->_arg_pos); - } - } - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } - return result; -} - -static llvm::Value* ProcessParamExpr(JitLlvmCodeGenContext* ctx, const JitParamExpr* expr, int* max_arg) -{ - llvm::Value* result = AddGetDatumParam(ctx, expr->_param_id, expr->_arg_pos); - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } - return result; -} - -static llvm::Value* ProcessVarExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, const JitVarExpr* expr, int* max_arg) -{ - llvm::Value* result = nullptr; - if (row == nullptr) { + // this is a bit awkward, but it works + MOT_LOG_TRACE("ProcessVarExpr(): Expression table %p [%s]", expr->_table, expr->_table->GetTableName().c_str()); + MOT_LOG_TRACE("ProcessVarExpr(): Outer table %p [%s]", + ctx->_table_info.m_table, + ctx->_table_info.m_table->GetTableName().c_str()); + MOT_LOG_TRACE("ProcessVarExpr(): row=%p, inner-row=%p", row, innerRow); + llvm::Value* table = (expr->_table == ctx->_table_info.m_table) ? ctx->table_value : ctx->inner_table_value; + llvm::Value* usedRow = (expr->_table == ctx->_table_info.m_table) ? row : innerRow; + if (usedRow == nullptr) { MOT_LOG_TRACE("ProcessVarExpr(): Unexpected VAR expression without a row"); } else { - // this is a bit awkward, but it works - llvm::Value* table = (expr->_table == ctx->_table_info.m_table) ? ctx->table_value : ctx->inner_table_value; - result = AddReadDatumColumn(ctx, table, row, expr->_column_id, expr->_arg_pos); - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } + MOT_LOG_DEBUG("ProcessVarExpr(): Using row %p and table %p", usedRow, table); + int isInnerRow = (usedRow == row) ? 0 : 1; + int subQueryIndex = -1; + result = AddReadDatumColumn(ctx, table, usedRow, expr->_column_id, isInnerRow, subQueryIndex); + AddSetExprCollation(ctx, expr->m_collationId); } return result; } -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecUnaryOperator(ctx, args[0], ctx->_builtin_##name, expr->_arg_pos); \ - break; - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecBinaryOperator(ctx, args[0], args[1], ctx->_builtin_##name, expr->_arg_pos); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = AddExecTernaryOperator(ctx, args[0], args[1], args[2], ctx->_builtin_##name, expr->_arg_pos); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) - -static llvm::Value* ProcessOpExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitOpExpr* expr, int* max_arg) +static llvm::Value* ProcessFuncCall(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, Oid functionId, + int argCount, JitExpr** args, Oid functionCollationId, Oid resultCollationId, const char* exprName) { llvm::Value* result = nullptr; + llvm::Value** argArray = nullptr; + llvm::Value** argIsNull = nullptr; + Oid* argTypes = nullptr; - llvm::Value* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - - for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], max_arg); - if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process operator sub-expression %d", arg_num); + if (argCount > 0) { + size_t allocSize = sizeof(llvm::Value*) * argCount; + argArray = (llvm::Value**)MOT::MemSessionAlloc(allocSize); + if (argArray == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d arguments in %s expression", + allocSize, + argCount, + exprName); return nullptr; } + + argIsNull = (llvm::Value**)MOT::MemSessionAlloc(allocSize); + if (argIsNull == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d argument nulls in %s expression", + allocSize, + argCount, + exprName); + MOT::MemSessionFree(argArray); + return nullptr; + } + + allocSize = sizeof(Oid) * argCount; + argTypes = (Oid*)MOT::MemSessionAlloc(allocSize); + if (argTypes == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d argument types in %s expression", + allocSize, + argCount, + exprName); + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + return nullptr; + } + + for (int i = 0; i < argCount; ++i) { + argArray[i] = ProcessExpr(ctx, row, innerRow, args[i]); + if (argArray[i] == nullptr) { + MOT_LOG_TRACE("Failed to process %s sub-expression %d", exprName, i); + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + MOT::MemSessionFree(argTypes); + return nullptr; + } + argIsNull[i] = AddGetExprIsNull(ctx); + argTypes[i] = args[i]->_result_type; + } } - switch (expr->_op_func_id) { - APPLY_OPERATORS() + result = AddInvokePGFunction( + ctx, functionId, argCount, functionCollationId, resultCollationId, argArray, argIsNull, argTypes); - default: - MOT_LOG_TRACE("Unsupported operator function type: %d", expr->_op_func_id); - break; + if (argCount > 0) { + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + MOT::MemSessionFree(argTypes); } return result; } -static llvm::Value* ProcessFuncExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFuncExpr* expr, int* max_arg) +static llvm::Value* ProcessOpExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitOpExpr* expr) { - llvm::Value* result = nullptr; - - llvm::Value* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - - for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], max_arg); - if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process function sub-expression %d", arg_num); - return nullptr; - } - } - - switch (expr->_func_id) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported function type: %d", expr->_func_id); - break; - } - - return result; + return ProcessFuncCall(ctx, + row, + innerRow, + expr->_op_func_id, + expr->_arg_count, + expr->_args, + expr->m_opCollationId, + expr->m_collationId, + "operator"); } -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR +static llvm::Value* ProcessFuncExpr( + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitFuncExpr* expr) +{ + return ProcessFuncCall(ctx, + row, + innerRow, + expr->_func_id, + expr->_arg_count, + expr->_args, + expr->m_funcCollationId, + expr->m_collationId, + "function"); +} -static llvm::Value* ProcessSubLinkExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitSubLinkExpr* expr, int* max_arg) +static llvm::Value* ProcessSubLinkExpr(JitLlvmCodeGenContext* ctx, JitSubLinkExpr* expr) { return AddSelectSubQueryResult(ctx, expr->_sub_query_index); } -static llvm::Value* ProcessBoolExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitBoolExpr* expr, int* maxArg) +static llvm::Value* ProcessBoolNotExpr( + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitBoolExpr* expr) { llvm::Value* result = nullptr; + llvm::Value* arg = ProcessExpr(ctx, row, innerRow, expr->_args[0]); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process Boolean NOT sub-expression"); + return nullptr; + } + llvm::Value* argIsNull = AddGetExprIsNull(ctx); + + // if any argument is null then result is null - so we need a local var + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + JIT_IF_BEGIN(arg_is_null); + JIT_IF_EVAL(argIsNull); + { + AddSetExprIsNull(ctx, 1); + } + JIT_ELSE(); + { + AddSetExprIsNull(ctx, 0); + AddSetExprCollation(ctx, InvalidOid); + llvm::Value* typedZero = llvm::ConstantInt::get(arg->getType(), 0, true); + llvm::Value* notResult = ctx->m_builder->CreateICmpEQ(arg, typedZero); // equivalent to NOT + result = ctx->m_builder->CreateIntCast(notResult, arg->getType(), true); + ctx->m_builder->CreateStore(result, boolResult, true); + } + JIT_IF_END(); + + return ctx->m_builder->CreateLoad(boolResult, true); +} + +static llvm::Value* ProcessBoolExpr( + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitBoolExpr* expr) +{ + if (expr->_bool_expr_type == NOT_EXPR) { + return ProcessBoolNotExpr(ctx, row, innerRow, expr); + } llvm::Value* args[MOT_JIT_MAX_BOOL_EXPR_ARGS] = {nullptr, nullptr}; + llvm::Value* argIsNull[MOT_JIT_MAX_BOOL_EXPR_ARGS] = {nullptr, nullptr}; int argNum = 0; for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], maxArg); + args[i] = ProcessExpr(ctx, row, innerRow, expr->_args[i]); if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process boolean sub-expression %d", argNum); + MOT_LOG_TRACE("Failed to process Boolean sub-expression %d", argNum); return nullptr; } + argIsNull[i] = AddGetExprIsNull(ctx); } - llvm::Value* typedZero = llvm::ConstantInt::get(args[0]->getType(), 0, true); - switch (expr->_bool_expr_type) { - case NOT_EXPR: { - llvm::Value* notResult = ctx->_builder->CreateICmpEQ(args[0], typedZero); // equivalent to NOT - result = ctx->_builder->CreateIntCast(notResult, args[0]->getType(), true); - break; + // if any argument is null then result is null - so we need a local var + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + JIT_IF_BEGIN(lhs_arg_is_null); + JIT_IF_EVAL(argIsNull[0]); + { + AddSetExprIsNull(ctx, 1); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(rhs_arg_is_null); + JIT_IF_EVAL(argIsNull[1]); + { + AddSetExprIsNull(ctx, 1); } + JIT_ELSE(); + { + AddSetExprIsNull(ctx, 0); + AddSetExprCollation(ctx, InvalidOid); + llvm::Value* typedZero = llvm::ConstantInt::get(args[0]->getType(), 0, true); + switch (expr->_bool_expr_type) { + case AND_EXPR: { + llvm::Value* result = ctx->m_builder->CreateSelect(args[0], args[1], typedZero); + ctx->m_builder->CreateStore(result, boolResult, true); + break; + } - case AND_EXPR: - result = ctx->_builder->CreateSelect(args[0], args[1], typedZero); - break; + case OR_EXPR: { + llvm::Value* typedOne = llvm::ConstantInt::get(args[0]->getType(), 1, true); + llvm::Value* result = ctx->m_builder->CreateSelect(args[0], typedOne, args[1]); + ctx->m_builder->CreateStore(result, boolResult, true); + break; + } - case OR_EXPR: { - llvm::Value* typedOne = llvm::ConstantInt::get(args[0]->getType(), 1, true); - result = ctx->_builder->CreateSelect(args[0], typedOne, args[1]); - break; + default: + MOT_LOG_TRACE("Unsupported boolean expression type: %d", (int)expr->_bool_expr_type); + boolResult = nullptr; + break; + } } + JIT_IF_END(); + } + JIT_IF_END(); - default: - MOT_LOG_TRACE("Unsupported boolean expression type: %d", (int)expr->_bool_expr_type); - break; + return ctx->m_builder->CreateLoad(boolResult, true); +} + +static llvm::Value* ProcessScalarArrayOpExpr( + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitScalarArrayOpExpr* expr) +{ + // if the array is empty, we do not need to process anything, just return true or false according to useOr + if (expr->m_arraySize == 0) { + return JIT_CONST_INT64(BoolGetDatum(expr->m_useOr)); } + // process the scalar value + llvm::Value* scalarValue = ProcessExpr(ctx, row, innerRow, expr->m_scalar); + if (scalarValue == nullptr) { + MOT_LOG_TRACE("ProcessScalarArrayOpExpr(): Failed to process scalar value in array operation"); + return nullptr; + } + llvm::Value* scalarIsNull = AddGetExprIsNull(ctx); + Oid scalarType = expr->m_scalar->_result_type; + + // get comparison function + uint32_t argCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(expr->_op_func_id, &argCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("ProcessScalarArrayOpExpr(): Cannot find function by id: %u", (unsigned)expr->_op_func_id); + return nullptr; + } + MOT_ASSERT(argCount == 2); + if (argCount != 2) { + MOT_LOG_TRACE("ProcessScalarArrayOpExpr(): Unexpected argument count %u in function %u", + argCount, + (unsigned)expr->_op_func_id); + return nullptr; + } + + // we must define a local variable for this to work properly + llvm::Value* scalarResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "scalar_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(!expr->m_useOr)), scalarResult, true); + + // evaluate result + JIT_IF_BEGIN(scalar_lhs_is_null); + // if the scalar is NULL, and the function is strict, return NULL + // for simpler logic, we embed the isStrict constant in the generated code + llvm::BasicBlock* postIfBlock = JIT_IF_POST_BLOCK(); + llvm::Value* cond = ctx->m_builder->CreateSelect(JIT_CONST_INT1(isStrict), scalarIsNull, JIT_CONST_INT32(0)); + JIT_IF_EVAL(cond); + { + // if the scalar is NULL, and the function is strict, return NULL + AddSetExprIsNull(ctx, 1); + } + JIT_ELSE(); + { + // iterate over array elements, evaluate each one, and compare to LHS value + AddSetExprIsNull(ctx, 0); // initially result is not null + AddSetExprCollation(ctx, expr->m_collationId); + for (int i = 0; i < expr->m_arraySize; ++i) { + JitExpr* e = expr->m_arrayElements[i]; + llvm::Value* element = ProcessExpr(ctx, row, innerRow, e); + llvm::Value* elementIsNull = AddGetExprIsNull(ctx); + + // invoke function + llvm::Value* cmpRes = AddInvokePGFunction2(ctx, + funcPtr, + expr->m_funcCollationId, + isStrict, + scalarValue, + scalarIsNull, + scalarType, + element, + elementIsNull, + e->_result_type); + llvm::Value* cmpResNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(cmp_res_null); + JIT_IF_EVAL(cmpResNull); + { + // if comparison result is null, then entire result is null + AddSetExprIsNull(ctx, 1); + JIT_GOTO(postIfBlock); + } + JIT_ELSE(); + { + if (expr->m_useOr) { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), scalarResult); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } else { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL_NOT(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), scalarResult); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } + } + JIT_IF_END(); + } + } + JIT_IF_END(); + return ctx->m_builder->CreateLoad(scalarResult, true); +} + +static llvm::Value* ProcessCoerceViaIOExpr( + JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitCoerceViaIOExpr* expr) +{ + llvm::Value* arg = ProcessExpr(ctx, row, innerRow, expr->m_arg); + llvm::Value* result = AddConvertViaString( + ctx, arg, JIT_CONST_INT32(expr->m_arg->_result_type), JIT_CONST_INT32(expr->_result_type), JIT_CONST_INT32(-1)); + if (result != nullptr) { + AddSetExprCollation(ctx, expr->m_collationId); + } return result; } -static llvm::Value* ProcessExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitExpr* expr, int* max_arg) +llvm::Value* ProcessExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitExpr* expr) { llvm::Value* result = nullptr; if (expr->_expr_type == JIT_EXPR_TYPE_CONST) { - result = ProcessConstExpr(ctx, (JitConstExpr*)expr, max_arg); + result = ProcessConstExpr(ctx, (JitConstExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_PARAM) { - result = ProcessParamExpr(ctx, (JitParamExpr*)expr, max_arg); + result = ProcessParamExpr(ctx, (JitParamExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_VAR) { - result = ProcessVarExpr(ctx, row, (JitVarExpr*)expr, max_arg); + result = ProcessVarExpr(ctx, row, innerRow, (JitVarExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_OP) { - result = ProcessOpExpr(ctx, row, (JitOpExpr*)expr, max_arg); + result = ProcessOpExpr(ctx, row, innerRow, (JitOpExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_FUNC) { - result = ProcessFuncExpr(ctx, row, (JitFuncExpr*)expr, max_arg); + result = ProcessFuncExpr(ctx, row, innerRow, (JitFuncExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_SUBLINK) { - result = ProcessSubLinkExpr(ctx, row, (JitSubLinkExpr*)expr, max_arg); + result = ProcessSubLinkExpr(ctx, (JitSubLinkExpr*)expr); } else if (expr->_expr_type == JIT_EXPR_TYPE_BOOL) { - result = ProcessBoolExpr(ctx, row, (JitBoolExpr*)expr, max_arg); + result = ProcessBoolExpr(ctx, row, innerRow, (JitBoolExpr*)expr); + } else if (expr->_expr_type == JIT_EXPR_TYPE_SCALAR_ARRAY_OP) { + result = ProcessScalarArrayOpExpr(ctx, row, innerRow, (JitScalarArrayOpExpr*)expr); + } else if (expr->_expr_type == JIT_EXPR_TYPE_COERCE_VIA_IO) { + result = ProcessCoerceViaIOExpr(ctx, row, innerRow, (JitCoerceViaIOExpr*)expr); } else { MOT_LOG_TRACE( "Failed to generate jitted code for query: unsupported target expression type: %d", (int)expr->_expr_type); @@ -976,10 +829,10 @@ static llvm::Value* ProcessExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, Ji return result; } -bool buildScanExpression(JitLlvmCodeGenContext* ctx, JitColumnExpr* expr, int* max_arg, - JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, llvm::Value* outer_row, int subQueryIndex) +bool buildScanExpression(JitLlvmCodeGenContext* ctx, JitColumnExpr* expr, JitRangeIteratorType range_itr_type, + JitRangeScanType range_scan_type, llvm::Value* outer_row, int subQueryIndex) { - llvm::Value* value = ProcessExpr(ctx, outer_row, expr->_expr, max_arg); + llvm::Value* value = ProcessExpr(ctx, outer_row, nullptr, expr->_expr); if (value == nullptr) { MOT_LOG_TRACE("buildScanExpression(): Failed to process expression with type %d", (int)expr->_expr->_expr_type); return false; @@ -1002,8 +855,8 @@ bool buildScanExpression(JitLlvmCodeGenContext* ctx, JitColumnExpr* expr, int* m return true; } -bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, int* maxArg, - JitRangeScanType rangeScanType, llvm::Value* outerRow, int exprCount /* = -1 */, int subQueryIndex /* = -1 */) +bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, JitRangeScanType rangeScanType, + llvm::Value* outerRow, int exprCount /* = -1 */, int subQueryIndex /* = -1 */) { if (exprCount == -1) { exprCount = exprArray->_count; @@ -1035,7 +888,7 @@ bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, i } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { if (expr->_table != ctx->m_subQueryTableInfo[subQueryIndex].m_table) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate TVM JIT Code", + "Generate LLVM JIT Code", "Invalid expression table (expected sub-query table %s, got %s)", ctx->m_subQueryTableInfo[subQueryIndex].m_table->GetTableName().c_str(), expr->_table->GetTableName().c_str()); @@ -1044,20 +897,19 @@ bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, i } // prepare the sub-expression - if (!buildScanExpression(ctx, expr, maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex)) { + if (!buildScanExpression(ctx, expr, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex)) { return false; } } return true; } -bool writeRowColumns( - JitLlvmCodeGenContext* ctx, llvm::Value* row, JitColumnExprArray* expr_array, int* max_arg, bool is_update) +bool writeRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitColumnExprArray* expr_array, bool is_update) { for (int i = 0; i < expr_array->_count; ++i) { JitColumnExpr* column_expr = &expr_array->_exprs[i]; - llvm::Value* value = ProcessExpr(ctx, row, column_expr->_expr, max_arg); + llvm::Value* value = ProcessExpr(ctx, row, nullptr, column_expr->_expr); if (value == nullptr) { MOT_LOG_TRACE("ProcessExpr() returned nullptr"); return false; @@ -1075,42 +927,28 @@ bool writeRowColumns( return true; } -bool selectRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitSelectExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, int subQueryIndex /* = -1 */) +bool selectRowColumns( + JitLlvmCodeGenContext* ctx, llvm::Value* row, JitSelectExprArray* expr_array, llvm::Value* innerRow /* = nullptr */) { - bool result = true; for (int i = 0; i < expr_array->_count; ++i) { JitSelectExpr* expr = &expr_array->_exprs[i]; - // we skip expressions that select from other tables - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - if (expr->_column_expr->_table != ctx->_inner_table_info.m_table) { - continue; - } - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (expr->_column_expr->_table != ctx->_table_info.m_table) { - continue; - } - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (expr->_column_expr->_table != ctx->m_subQueryTableInfo[subQueryIndex].m_table) { - continue; - } - } - result = AddSelectColumn( - ctx, row, expr->_column_expr->_column_id, expr->_tuple_column_id, range_scan_type, subQueryIndex); - if (!result) { - break; + llvm::Value* value = ProcessExpr(ctx, row, innerRow, expr->_expr); + if (value == nullptr) { + MOT_LOG_TRACE("selectRowColumns(): Failed to process expression %d", i); + return false; } + llvm::Value* valueIsNull = AddGetExprIsNull(ctx); + AddWriteTupleDatum(ctx, expr->_tuple_column_id, value, valueIsNull); } - return result; + return true; } -static bool buildClosedRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, llvm::Value* outer_row, int subQueryIndex) +static bool buildClosedRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, JitRangeScanType range_scan_type, + llvm::Value* outer_row, int subQueryIndex) { // a closed range scan starts just like a point scan (without enough search expressions) and then adds key patterns - bool result = - buildPointScan(ctx, &index_scan->_search_exprs, max_arg, range_scan_type, outer_row, -1, subQueryIndex); + bool result = buildPointScan(ctx, &index_scan->_search_exprs, range_scan_type, outer_row, -1, subQueryIndex); if (result) { AddCopyKey(ctx, range_scan_type, subQueryIndex); @@ -1160,7 +998,7 @@ static bool buildClosedRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index return result; } -static void BuildAscendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, +static void BuildAscendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex, int offset, int size, JitColumnExpr* lastExpr) { @@ -1168,14 +1006,14 @@ static void BuildAscendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndex // this is an upper bound operator on an ascending semi-open scan so we fill the begin key with zeros, // and the end key with the value AddFillKeyPattern(ctx, 0x00, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); + buildScanExpression(ctx, lastExpr, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; *endRangeBound = (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; } else { // this is a lower bound operator on an ascending semi-open scan so we fill the begin key with the // value, and the end key with 0xFF - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); + buildScanExpression(ctx, lastExpr, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); AddFillKeyPattern(ctx, 0xFF, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); *beginRangeBound = (indexScan->_last_dim_op1 == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; @@ -1183,14 +1021,14 @@ static void BuildAscendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndex } } -static void BuildDescendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, +static void BuildDescendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex, int offset, int size, JitColumnExpr* lastExpr) { if ((indexScan->_last_dim_op1 == JIT_WOC_LESS_THAN) || (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS)) { // this is an upper bound operator on a descending semi-open scan so we fill the begin key with value, // and the end key with zeroes - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); + buildScanExpression(ctx, lastExpr, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); AddFillKeyPattern(ctx, 0x00, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); *beginRangeBound = (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; @@ -1199,29 +1037,24 @@ static void BuildDescendingSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitInde // this is a lower bound operator on a descending semi-open scan so we fill the begin key with 0xFF, and // the end key with the value AddFillKeyPattern(ctx, 0xFF, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); + buildScanExpression(ctx, lastExpr, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; *endRangeBound = (indexScan->_last_dim_op1 == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; } } -static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - llvm::Value* outerRow, int subQueryIndex) +static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, + JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex) { // an open range scan starts just like a point scan (with not enough search expressions) and then adds key patterns // we do not use the last search expression - bool result = buildPointScan(ctx, - &indexScan->_search_exprs, - maxArg, - rangeScanType, - outerRow, - indexScan->_search_exprs._count - 1, - subQueryIndex); + bool result = buildPointScan( + ctx, &indexScan->_search_exprs, rangeScanType, outerRow, indexScan->_search_exprs._count - 1, subQueryIndex); if (result) { AddCopyKey(ctx, rangeScanType, subQueryIndex); - + unsigned char startPattern; + unsigned char endPattern; // now fill each key with the right pattern for the missing pkey columns in the where clause bool ascending = (indexScan->_sort_order == JIT_QUERY_SORT_ASCENDING); @@ -1253,7 +1086,6 @@ static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* ind if (ascending) { BuildAscendingSemiOpenRangeScan(ctx, indexScan, - maxArg, rangeScanType, beginRangeBound, endRangeBound, @@ -1265,7 +1097,6 @@ static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* ind } else { BuildDescendingSemiOpenRangeScan(ctx, indexScan, - maxArg, rangeScanType, beginRangeBound, endRangeBound, @@ -1276,6 +1107,16 @@ static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* ind last_expr); } + if (*beginRangeBound == JIT_RANGE_BOUND_INCLUDE) { + startPattern = (ascending ? 0x00 : 0xFF); + } else { + startPattern = (ascending ? 0xFF : 0x00); + } + if (*endRangeBound == JIT_RANGE_BOUND_INCLUDE) { + endPattern = (ascending ? 0xFF : 0x00); + } else { + endPattern = (ascending ? 0x00 : 0xFF); + } // now fill the rest as usual int first_zero_column = indexScan->_column_count; for (int i = first_zero_column; i < index_column_count; ++i) { @@ -1283,19 +1124,17 @@ static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* ind int size = key_length[i]; MOT_LOG_DEBUG( "Filling begin/end iterator pattern for missing pkey fields at offset %d, size %d", offset, size); - AddFillKeyPattern( - ctx, ascending ? 0x00 : 0xFF, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - AddFillKeyPattern( - ctx, ascending ? 0xFF : 0x00, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); + AddFillKeyPattern(ctx, startPattern, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); + AddFillKeyPattern(ctx, endPattern, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); } AddAdjustKey(ctx, - ascending ? 0x00 : 0xFF, + startPattern, JIT_RANGE_ITERATOR_START, rangeScanType, // currently this is relevant only for secondary index searches subQueryIndex); AddAdjustKey(ctx, - ascending ? 0xFF : 0x00, + endPattern, JIT_RANGE_ITERATOR_END, rangeScanType, // currently this is relevant only for secondary index searches subQueryIndex); @@ -1303,7 +1142,7 @@ static bool buildSemiOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* ind return result; } -static void BuildAscendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, +static void BuildAscendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex, JitWhereOperatorClass beforeLastDimOp, JitWhereOperatorClass lastDimOp, JitColumnExpr* beforeLastExpr, JitColumnExpr* lastExpr) @@ -1312,16 +1151,15 @@ static void BuildAscendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan MOT_ASSERT((lastDimOp == JIT_WOC_GREATER_THAN) || (lastDimOp == JIT_WOC_GREATER_EQUALS)); // the before-last operator is an upper bound operator on an ascending open scan so we fill the begin // key with the last value, and the end key with the before-last value + MOT_LOG_TRACE("Building inverted ascending open range scan"); buildScanExpression(ctx, lastExpr, - maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, // lower bound on begin iterator key subQueryIndex); buildScanExpression(ctx, beforeLastExpr, - maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, // upper bound on end iterator key @@ -1329,19 +1167,18 @@ static void BuildAscendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan *beginRangeBound = (lastDimOp == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; *endRangeBound = (beforeLastDimOp == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; } else { + MOT_LOG_TRACE("Building regular ascending open range scan"); MOT_ASSERT((lastDimOp == JIT_WOC_LESS_THAN) || (lastDimOp == JIT_WOC_LESS_EQUALS)); // the before-last operator is a lower bound operator on an ascending open scan so we fill the begin key // with the before-last value, and the end key with the last value buildScanExpression(ctx, beforeLastExpr, - maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, // lower bound on begin iterator key subQueryIndex); buildScanExpression(ctx, lastExpr, - maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, // upper bound on end iterator key @@ -1352,7 +1189,7 @@ static void BuildAscendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan } } -static void BuildDescendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, +static void BuildDescendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex, JitWhereOperatorClass beforeLastDimOp, JitWhereOperatorClass lastDimOp, JitColumnExpr* beforeLastExpr, JitColumnExpr* lastExpr) @@ -1363,14 +1200,12 @@ static void BuildDescendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexSca // key with the last value, and the end key with the before-last value buildScanExpression(ctx, beforeLastExpr, - maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, // upper bound on begin iterator key subQueryIndex); buildScanExpression(ctx, lastExpr, - maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, // lower bound on end iterator key @@ -1383,14 +1218,12 @@ static void BuildDescendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexSca // key with the last value, and the end key with the before-last value buildScanExpression(ctx, lastExpr, - maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, // upper bound on begin iterator key subQueryIndex); buildScanExpression(ctx, beforeLastExpr, - maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, // lower bound on end iterator key @@ -1401,22 +1234,22 @@ static void BuildDescendingOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexSca } } -static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, JitRangeBoundMode* begin_range_bound, JitRangeBoundMode* end_range_bound, - llvm::Value* outer_row, int subQueryIndex) +static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, JitRangeScanType range_scan_type, + JitRangeBoundMode* begin_range_bound, JitRangeBoundMode* end_range_bound, llvm::Value* outer_row, int subQueryIndex) { // an open range scan starts just like a point scan (with not enough search expressions) and then adds key patterns // we do not use the last two expressions + MOT_LOG_TRACE("Starting with point scan"); bool result = buildPointScan(ctx, &index_scan->_search_exprs, - max_arg, range_scan_type, outer_row, index_scan->_search_exprs._count - 2, subQueryIndex); if (result) { AddCopyKey(ctx, range_scan_type, subQueryIndex); - + unsigned char startPattern; + unsigned char endPattern; // now fill each key with the right pattern for the missing pkey columns in the where clause bool ascending = (index_scan->_sort_order == JIT_QUERY_SORT_ASCENDING); @@ -1428,6 +1261,7 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s key_length = ctx->_inner_table_info.m_index->GetLengthKeyFields(); index_column_count = ctx->_inner_table_info.m_index->GetNumFields(); } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { + MOT_LOG_TRACE("Building main table scan"); index_column_offsets = ctx->_table_info.m_indexColumnOffsets; key_length = ctx->_table_info.m_index->GetLengthKeyFields(); index_column_count = ctx->_table_info.m_index->GetNumFields(); @@ -1444,9 +1278,9 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s JitColumnExpr* last_expr = &index_scan->_search_exprs._exprs[last_expr_index]; JitColumnExpr* before_last_expr = &index_scan->_search_exprs._exprs[last_expr_index - 1]; if (ascending) { + MOT_LOG_TRACE("Building ascending open range scan"); BuildAscendingOpenRangeScan(ctx, index_scan, - max_arg, range_scan_type, begin_range_bound, end_range_bound, @@ -1457,9 +1291,9 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s before_last_expr, last_expr); } else { + MOT_LOG_TRACE("Building descending open range scan"); BuildDescendingOpenRangeScan(ctx, index_scan, - max_arg, range_scan_type, begin_range_bound, end_range_bound, @@ -1471,6 +1305,16 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s last_expr); } + if (*begin_range_bound == JIT_RANGE_BOUND_INCLUDE) { + startPattern = (ascending ? 0x00 : 0xFF); + } else { + startPattern = (ascending ? 0xFF : 0x00); + } + if (*end_range_bound == JIT_RANGE_BOUND_INCLUDE) { + endPattern = (ascending ? 0xFF : 0x00); + } else { + endPattern = (ascending ? 0x00 : 0xFF); + } // now fill the rest as usual int first_zero_column = index_scan->_column_count; for (int i = first_zero_column; i < index_column_count; ++i) { @@ -1479,18 +1323,17 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s MOT_LOG_DEBUG( "Filling begin/end iterator pattern for missing pkey fields at offset %d, size %d", offset, size); AddFillKeyPattern( - ctx, ascending ? 0x00 : 0xFF, offset, size, JIT_RANGE_ITERATOR_START, range_scan_type, subQueryIndex); - AddFillKeyPattern( - ctx, ascending ? 0xFF : 0x00, offset, size, JIT_RANGE_ITERATOR_END, range_scan_type, subQueryIndex); + ctx, startPattern, offset, size, JIT_RANGE_ITERATOR_START, range_scan_type, subQueryIndex); + AddFillKeyPattern(ctx, endPattern, offset, size, JIT_RANGE_ITERATOR_END, range_scan_type, subQueryIndex); } AddAdjustKey(ctx, - ascending ? 0x00 : 0xFF, + startPattern, JIT_RANGE_ITERATOR_START, range_scan_type, // currently this is relevant only for secondary index searches subQueryIndex); AddAdjustKey(ctx, - ascending ? 0xFF : 0x00, + endPattern, JIT_RANGE_ITERATOR_END, range_scan_type, // currently this is relevant only for secondary index searches subQueryIndex); @@ -1498,177 +1341,205 @@ static bool buildOpenRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_s return result; } -static bool buildRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, JitRangeBoundMode* begin_range_bound, JitRangeBoundMode* end_range_bound, - llvm::Value* outer_row, int subQueryIndex = -1) +static bool buildRangeScan(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, JitRangeScanType rangeScanType, + JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, llvm::Value* outerRow, int subQueryIndex = -1) { bool result = false; // if this is a point scan we generate two identical keys for the iterators - if (index_scan->_scan_type == JIT_INDEX_SCAN_POINT) { - result = - buildPointScan(ctx, &index_scan->_search_exprs, max_arg, range_scan_type, outer_row, -1, subQueryIndex); + if (indexScan->_scan_type == JIT_INDEX_SCAN_POINT) { + MOT_LOG_TRACE("Building point scan"); + result = buildPointScan(ctx, &indexScan->_search_exprs, rangeScanType, outerRow, -1, subQueryIndex); if (result) { - AddCopyKey(ctx, range_scan_type, subQueryIndex); - *begin_range_bound = JIT_RANGE_BOUND_INCLUDE; - *end_range_bound = JIT_RANGE_BOUND_INCLUDE; + AddCopyKey(ctx, rangeScanType, subQueryIndex); + *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; + *endRangeBound = JIT_RANGE_BOUND_INCLUDE; } - } else if (index_scan->_scan_type == JIT_INDEX_SCAN_CLOSED) { - result = buildClosedRangeScan(ctx, index_scan, max_arg, range_scan_type, outer_row, subQueryIndex); + } else if (indexScan->_scan_type == JIT_INDEX_SCAN_CLOSED) { + MOT_LOG_TRACE("Building closed range scan"); + result = buildClosedRangeScan(ctx, indexScan, rangeScanType, outerRow, subQueryIndex); if (result) { - *begin_range_bound = JIT_RANGE_BOUND_INCLUDE; - *end_range_bound = JIT_RANGE_BOUND_INCLUDE; + *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; + *endRangeBound = JIT_RANGE_BOUND_INCLUDE; } - } else if (index_scan->_scan_type == JIT_INDEX_SCAN_SEMI_OPEN) { + } else if (indexScan->_scan_type == JIT_INDEX_SCAN_SEMI_OPEN) { + MOT_LOG_TRACE("Building semi-open range scan"); result = buildSemiOpenRangeScan( - ctx, index_scan, max_arg, range_scan_type, begin_range_bound, end_range_bound, outer_row, subQueryIndex); - } else if (index_scan->_scan_type == JIT_INDEX_SCAN_OPEN) { - result = buildOpenRangeScan( - ctx, index_scan, max_arg, range_scan_type, begin_range_bound, end_range_bound, outer_row, subQueryIndex); - } else if (index_scan->_scan_type == JIT_INDEX_SCAN_FULL) { + ctx, indexScan, rangeScanType, beginRangeBound, endRangeBound, outerRow, subQueryIndex); + } else if (indexScan->_scan_type == JIT_INDEX_SCAN_OPEN) { + MOT_LOG_TRACE("Building open range scan"); + result = + buildOpenRangeScan(ctx, indexScan, rangeScanType, beginRangeBound, endRangeBound, outerRow, subQueryIndex); + } else if (indexScan->_scan_type == JIT_INDEX_SCAN_FULL) { + MOT_LOG_TRACE("Building full range scan"); result = true; // no keys used - *begin_range_bound = JIT_RANGE_BOUND_INCLUDE; - *end_range_bound = JIT_RANGE_BOUND_INCLUDE; + *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; + *endRangeBound = JIT_RANGE_BOUND_INCLUDE; } return result; } -static bool buildPrepareStateScan(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, llvm::Value* outer_row) +static bool buildPrepareStateScan( + JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, JitRangeScanType range_scan_type, llvm::Value* outer_row) { JitRangeBoundMode begin_range_bound = JIT_RANGE_BOUND_NONE; JitRangeBoundMode end_range_bound = JIT_RANGE_BOUND_NONE; // emit code to check if state iterators are null - JIT_IF_BEGIN(state_iterators_exist) + JIT_IF_BEGIN(state_iterators_exist); llvm::Value* is_begin_itr_null = AddIsStateIteratorNull(ctx, JIT_RANGE_ITERATOR_START, range_scan_type); - JIT_IF_EVAL(is_begin_itr_null) - // prepare search keys - if (!buildRangeScan(ctx, index_scan, max_arg, range_scan_type, &begin_range_bound, &end_range_bound, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for range select query: unsupported WHERE clause type"); - return false; - } + JIT_IF_EVAL(is_begin_itr_null); + { + // prepare search keys + if (!buildRangeScan(ctx, index_scan, range_scan_type, &begin_range_bound, &end_range_bound, outer_row)) { + MOT_LOG_TRACE("Failed to generate jitted code for range select query: unsupported WHERE clause type"); + return false; + } - // search begin iterator and save it in execution state - IssueDebugLog("Building search iterator from search key, and saving in execution state"); - if (index_scan->_scan_type == JIT_INDEX_SCAN_FULL) { - llvm::Value* itr = buildBeginIterator(ctx, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); - } else { - llvm::Value* itr = buildSearchIterator(ctx, index_scan->_scan_direction, begin_range_bound, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); + // search begin iterator and save it in execution state + IssueDebugLog("Building search iterator from search key, and saving in execution state"); + if (index_scan->_scan_type == JIT_INDEX_SCAN_FULL) { + llvm::Value* itr = buildBeginIterator(ctx, range_scan_type); + AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); + } else { + llvm::Value* itr = + buildSearchIterator(ctx, index_scan->_scan_direction, begin_range_bound, range_scan_type); + AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); - // create end iterator and save it in execution state - IssueDebugLog("Creating end iterator from end search key, and saving in execution state"); - itr = AddCreateEndIterator(ctx, index_scan->_scan_direction, end_range_bound, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_END, range_scan_type); - } + // create end iterator and save it in execution state + IssueDebugLog("Creating end iterator from end search key, and saving in execution state"); + itr = AddCreateEndIterator(ctx, index_scan->_scan_direction, end_range_bound, range_scan_type); + AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_END, range_scan_type); + } - // initialize state scan variables - if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - AddResetStateLimitCounter(ctx); // in case there is a limit clause - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // in case this is a join query - AddSetStateScanEndFlag( - ctx, 0, JIT_RANGE_SCAN_MAIN); // reset state flag (only once, and not repeatedly if row filter failed) + // initialize state scan variables + if (range_scan_type == JIT_RANGE_SCAN_MAIN) { + AddResetStateLimitCounter(ctx); // in case there is a limit clause + AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // in case this is a join query + AddSetStateScanEndFlag( + ctx, 0, JIT_RANGE_SCAN_MAIN); // reset state flag (only once, and not repeatedly if row filter failed) + } } - JIT_IF_END() + JIT_IF_END(); return true; } static bool buildPrepareStateRow(JitLlvmCodeGenContext* ctx, MOT::AccessType access_mode, JitIndexScan* index_scan, - int* max_arg, JitRangeScanType range_scan_type, llvm::BasicBlock* next_block) + JitRangeScanType range_scan_type, llvm::BasicBlock* next_block, llvm::Value* outerRow, bool emitReturnOnFail) { - llvm::LLVMContext& context = ctx->_code_gen->context(); + llvm::LLVMContext& context = ctx->m_codeGen->context(); MOT_LOG_DEBUG("Generating select code for stateful range select"); llvm::Value* row = nullptr; // we start a new block so current block must end with terminator - DEFINE_BLOCK(prepare_state_row_bb, ctx->m_jittedQuery); - MOT_LOG_TRACE("Adding unconditional branch into prepare_state_row_bb from branch %s", - ctx->_builder->GetInsertBlock()->getName().data()); - ctx->_builder->CreateBr(prepare_state_row_bb); - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - MOT_LOG_BEGIN(MOT::LogLevel::LL_TRACE, "Added unconditional branch result:"); + DEFINE_BLOCK(prepare_state_row_bb, ctx->m_jittedFunction); + MOT_LOG_DEBUG("Adding unconditional branch into prepare_state_row_bb from branch %s", + ctx->m_builder->GetInsertBlock()->getName().data()); + ctx->m_builder->CreateBr(prepare_state_row_bb); + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { + MOT_LOG_BEGIN(MOT::LogLevel::LL_DEBUG, "Added unconditional branch result:"); std::string s; llvm::raw_string_ostream os(s); - ctx->_builder->GetInsertBlock()->back().print(os); - MOT_LOG_APPEND(MOT::LogLevel::LL_TRACE, "%s", s.c_str()); - MOT_LOG_END(MOT::LogLevel::LL_TRACE); + ctx->m_builder->GetInsertBlock()->back().print(os); + MOT_LOG_APPEND(MOT::LogLevel::LL_DEBUG, "%s", s.c_str()); + MOT_LOG_END(MOT::LogLevel::LL_DEBUG); } - ctx->_builder->SetInsertPoint(prepare_state_row_bb); + ctx->m_builder->SetInsertPoint(prepare_state_row_bb); // check if state row is nullptr - JIT_IF_BEGIN(test_state_row) + JIT_IF_BEGIN(test_state_row); IssueDebugLog("Checking if state row is nullptr"); llvm::Value* res = AddIsStateRowNull(ctx, range_scan_type); - JIT_IF_EVAL(res) - IssueDebugLog("State row is nullptr, fetching from state iterators"); + JIT_IF_EVAL(res); + { + IssueDebugLog("State row is nullptr, fetching from state iterators"); - // check if state scan ended - JIT_IF_BEGIN(test_scan) - IssueDebugLog("Checking if state scan ended"); - llvm::BasicBlock* isStateScanEndBlock = JIT_CURRENT_BLOCK(); // remember current block if filter fails - llvm::Value* res = AddIsStateScanEnd(ctx, index_scan->_scan_direction, range_scan_type); - JIT_IF_EVAL(res) - // fail scan block - IssueDebugLog("Scan ended, raising internal state scan end flag"); - AddSetStateScanEndFlag(ctx, 1, range_scan_type); - JIT_ELSE() - // now get row from iterator - IssueDebugLog("State scan not ended - Retrieving row from iterator"); - row = AddGetRowFromStateIterator(ctx, access_mode, index_scan->_scan_direction, range_scan_type); + // check if state scan ended + JIT_IF_BEGIN(test_scan); + IssueDebugLog("Checking if state scan ended"); + llvm::BasicBlock* isStateScanEndBlock = JIT_CURRENT_BLOCK(); // remember current block if filter fails + llvm::Value* res = AddIsStateScanEnd(ctx, index_scan->_scan_direction, range_scan_type); + JIT_IF_EVAL(res); + { + // fail scan block + IssueDebugLog("Scan ended, raising internal state scan end flag"); + AddSetStateScanEndFlag(ctx, 1, range_scan_type); + } + JIT_ELSE(); + { + // now get row from iterator + IssueDebugLog("State scan not ended - Retrieving row from iterator"); + row = AddGetRowFromStateIterator(ctx, access_mode, index_scan->_scan_direction, range_scan_type); - // check if row was found - JIT_IF_BEGIN(test_row_found) - JIT_IF_EVAL_NOT(row) - // row not found branch - IssueDebugLog("Could not retrieve row from state iterator, raising internal state scan end flag"); - AddSetStateScanEndFlag(ctx, 1, range_scan_type); - JIT_ELSE() - // row found, check for additional filters, if not passing filter then go back to execute IsStateScanEnd() - if (!buildFilterRow(ctx, row, &index_scan->_filters, max_arg, isStateScanEndBlock)) { - MOT_LOG_TRACE("Failed to generate jitted code for query: failed to build filter expressions for row"); - return false; + // check if row was found + JIT_IF_BEGIN(test_row_found); + JIT_IF_EVAL_NOT(row); + { + // row not found branch + IssueDebugLog("Could not retrieve row from state iterator, raising internal state scan end flag"); + AddSetStateScanEndFlag(ctx, 1, range_scan_type); + } + JIT_ELSE(); + { + // row found, check for additional filters, if not passing filter then go back to execute + // IsStateScanEnd() + llvm::Value* outerRowReal = + outerRow ? outerRow : ((range_scan_type != JIT_RANGE_SCAN_INNER) ? row : nullptr); + llvm::Value* innerRow = (range_scan_type == JIT_RANGE_SCAN_INNER) ? row : nullptr; + if (!buildFilterRow(ctx, outerRowReal, innerRow, &index_scan->_filters, isStateScanEndBlock)) { + MOT_LOG_TRACE( + "Failed to generate jitted code for query: failed to build filter expressions for row"); + return false; + } + // row passed all filters, so save it in state outer row + AddSetStateRow(ctx, row, range_scan_type); + if (range_scan_type == JIT_RANGE_SCAN_MAIN) { + AddSetStateScanEndFlag(ctx, 0, JIT_RANGE_SCAN_INNER); // reset inner scan flag for JOIN queries + } + } + JIT_IF_END(); + } + JIT_IF_END(); } - // row passed all filters, so save it in state outer row - AddSetStateRow(ctx, row, range_scan_type); - if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - AddSetStateScanEndFlag(ctx, 0, JIT_RANGE_SCAN_INNER); // reset inner scan flag for JOIN queries - } - JIT_IF_END() - JIT_IF_END() - JIT_IF_END() + JIT_IF_END(); // cleanup state iterators if needed and return/jump to next block - JIT_IF_BEGIN(state_scan_ended) + JIT_IF_BEGIN(state_scan_ended); IssueDebugLog("Checking if state scan ended flag was raised"); llvm::Value* state_scan_end_flag = AddGetStateScanEndFlag(ctx, range_scan_type); - JIT_IF_EVAL(state_scan_end_flag) - IssueDebugLog(" State scan ended flag was raised, cleaning up iterators and reporting to user"); - AddDestroyStateIterators(ctx, range_scan_type); // cleanup - if ((range_scan_type == JIT_RANGE_SCAN_MAIN) || (next_block == nullptr)) { - // either a main scan ended (simple range or outer loop of join), - // or an inner range ended in an outer point join (so next block is null) - // in either case let caller know scan ended and return appropriate value - AddSetScanEnded(ctx, 1); // no outer row, we are definitely done - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - } else { - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // make sure a new row is fetched in outer loop - ctx->_builder->CreateBr(next_block); // jump back to outer loop + JIT_IF_EVAL(state_scan_end_flag); + { + IssueDebugLog(" State scan ended flag was raised, cleaning up iterators and reporting to user"); + AddDestroyStateIterators(ctx, range_scan_type); // cleanup + if ((range_scan_type == JIT_RANGE_SCAN_MAIN) || (next_block == nullptr)) { + // either a main scan ended (simple range or outer loop of join), + // or an inner range ended in an outer point join (so next block is null) + // in either case let caller know scan ended and return appropriate value + AddSetScanEnded(ctx, 1); // no outer row, we are definitely done + if (emitReturnOnFail) { + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } else { + AddResetStateRow(ctx, range_scan_type); // put null in state row, whether during inner or outer loop + } + } else { + AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // make sure a new row is fetched in outer loop + ctx->m_builder->CreateBr(next_block); // jump back to outer loop + } } - JIT_IF_END() + JIT_IF_END(); return true; } llvm::Value* buildPrepareStateScanRow(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, - JitRangeScanType range_scan_type, MOT::AccessType access_mode, int* max_arg, llvm::Value* outer_row, - llvm::BasicBlock* next_block, llvm::BasicBlock** loop_block) + JitRangeScanType range_scan_type, MOT::AccessType access_mode, llvm::Value* outer_row, llvm::BasicBlock* next_block, + llvm::BasicBlock** loop_block, bool emitReturnOnFail /* = true */) { // prepare stateful scan if not done so already - if (!buildPrepareStateScan(ctx, index_scan, max_arg, range_scan_type, outer_row)) { + if (!buildPrepareStateScan(ctx, index_scan, range_scan_type, outer_row)) { MOT_LOG_TRACE("Failed to generate jitted code for range JOIN query: unsupported %s WHERE clause type", (range_scan_type == JIT_RANGE_SCAN_MAIN) ? "outer" : "inner"); return nullptr; @@ -1676,13 +1547,13 @@ llvm::Value* buildPrepareStateScanRow(JitLlvmCodeGenContext* ctx, JitIndexScan* // mark position for later jump if (loop_block != nullptr) { - *loop_block = llvm::BasicBlock::Create(ctx->_code_gen->context(), "fetch_outer_row_bb", ctx->m_jittedQuery); - ctx->_builder->CreateBr(*loop_block); // end current block - ctx->_builder->SetInsertPoint(*loop_block); // start new block + *loop_block = llvm::BasicBlock::Create(ctx->m_codeGen->context(), "fetch_outer_row_bb", ctx->m_jittedFunction); + ctx->m_builder->CreateBr(*loop_block); // end current block + ctx->m_builder->SetInsertPoint(*loop_block); // start new block } // fetch row for read - if (!buildPrepareStateRow(ctx, access_mode, index_scan, max_arg, range_scan_type, next_block)) { + if (!buildPrepareStateRow(ctx, access_mode, index_scan, range_scan_type, next_block, outer_row, emitReturnOnFail)) { MOT_LOG_TRACE("Failed to generate jitted code for range JOIN query: failed to build search outer row block"); return nullptr; } @@ -1690,50 +1561,53 @@ llvm::Value* buildPrepareStateScanRow(JitLlvmCodeGenContext* ctx, JitIndexScan* return row; } -JitLlvmRuntimeCursor buildRangeCursor(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitIndexScanDirection indexScanDirection, llvm::Value* outerRow, - int subQueryIndex /* = -1 */) +JitLlvmRuntimeCursor buildRangeCursor(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, + JitRangeScanType rangeScanType, llvm::Value* outerRow, int subQueryIndex /* = -1 */) { JitLlvmRuntimeCursor result = {nullptr, nullptr}; JitRangeBoundMode beginRangeBound = JIT_RANGE_BOUND_NONE; JitRangeBoundMode endRangeBound = JIT_RANGE_BOUND_NONE; - if (!buildRangeScan( - ctx, indexScan, maxArg, rangeScanType, &beginRangeBound, &endRangeBound, outerRow, subQueryIndex)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported %s-loop WHERE clause type", + JitIndexScanDirection indexScanDirection = indexScan->_scan_direction; + if (!buildRangeScan(ctx, indexScan, rangeScanType, &beginRangeBound, &endRangeBound, outerRow, subQueryIndex)) { + MOT_LOG_TRACE("Failed to generate jitted code for range query: unsupported %s-loop WHERE clause type", outerRow ? "inner" : "outer"); return result; } // build range iterators - result.begin_itr = buildSearchIterator(ctx, indexScanDirection, beginRangeBound, rangeScanType, subQueryIndex); - result.end_itr = - AddCreateEndIterator(ctx, indexScanDirection, endRangeBound, rangeScanType, subQueryIndex); // forward scan + if (indexScan->_scan_type == JIT_INDEX_SCAN_FULL) { + result.begin_itr = buildBeginIterator(ctx, rangeScanType); + result.end_itr = ctx->m_builder->CreateCast( + llvm::Instruction::IntToPtr, JIT_CONST_INT64(0), ctx->IndexIteratorType->getPointerTo()); + } else { + result.begin_itr = buildSearchIterator(ctx, indexScanDirection, beginRangeBound, rangeScanType, subQueryIndex); + result.end_itr = AddCreateEndIterator(ctx, indexScanDirection, endRangeBound, rangeScanType, subQueryIndex); + } return result; } -bool prepareAggregateAvg(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate) +bool prepareAggregateAvg(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, int aggIndex) { // although we already have this information in the aggregate descriptor, we still check again switch (aggregate->_func_id) { case INT8AVGFUNCOID: case NUMERICAVGFUNCOID: // the current_aggregate is a 2 numeric array - AddPrepareAvgArray(ctx, NUMERICOID, 2); + AddPrepareAvgArray(ctx, aggIndex, NUMERICOID, 2); break; case INT4AVGFUNCOID: case INT2AVGFUNCOID: - case 5537: // int1 avg + case INT1AVGFUNCOID: // the current_aggregate is a 2 int8 array - AddPrepareAvgArray(ctx, INT8OID, 2); + AddPrepareAvgArray(ctx, aggIndex, INT8OID, 2); break; - case 2104: // float4 - case 2105: // float8 + case FLOAT4AVGFUNCOID: + case FLOAT8AVGFUNCOID: // the current_aggregate is a 3 float8 array - AddPrepareAvgArray(ctx, FLOAT8OID, 3); + AddPrepareAvgArray(ctx, aggIndex, FLOAT8OID, 3); break; default: @@ -1744,32 +1618,33 @@ bool prepareAggregateAvg(JitLlvmCodeGenContext* ctx, const JitAggregate* aggrega return true; } -bool prepareAggregateSum(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate) +bool prepareAggregateSum(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, int aggIndex) { + // NOTE: there is NO sum function for type int1 switch (aggregate->_func_id) { case INT8SUMFUNCOID: // current sum in numeric, and value is int8, both can be null - AddResetAggValue(ctx, NUMERICOID); + AddResetAggValue(ctx, aggIndex, NUMERICOID); break; case INT4SUMFUNCOID: case INT2SUMFUNCOID: // current aggregate is a int8, and value is int4, both can be null - AddResetAggValue(ctx, INT8OID); + AddResetAggValue(ctx, aggIndex, INT8OID); break; case 2110: // float4 // current aggregate is a float4, and value is float4, both can **NOT** be null - AddResetAggValue(ctx, FLOAT4OID); + AddResetAggValue(ctx, aggIndex, FLOAT4OID); break; case 2111: // float8 // current aggregate is a float8, and value is float8, both can **NOT** be null - AddResetAggValue(ctx, FLOAT8OID); + AddResetAggValue(ctx, aggIndex, FLOAT8OID); break; case NUMERICSUMFUNCOID: - AddResetAggValue(ctx, NUMERICOID); + AddResetAggValue(ctx, aggIndex, NUMERICOID); // current aggregate is a numeric, and value is numeric, both can **NOT** be null break; @@ -1780,19 +1655,20 @@ bool prepareAggregateSum(JitLlvmCodeGenContext* ctx, const JitAggregate* aggrega return true; } -bool prepareAggregateMaxMin(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate) +bool prepareAggregateMaxMin(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, int aggIndex) { - AddResetAggMaxMinNull(ctx); + AddSetAggValueIsNull(ctx, aggIndex, JIT_CONST_INT32(1)); return true; } -bool prepareAggregateCount(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate) +bool prepareAggregateCount(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, int aggIndex) { switch (aggregate->_func_id) { case 2147: // int8inc_any case 2803: // int8inc // current aggregate is int8, and can **NOT** be null - AddResetAggValue(ctx, INT8OID); + AddResetAggValue(ctx, aggIndex, INT8OID); + AddSetAggValueIsNull(ctx, aggIndex, JIT_CONST_INT32(0)); break; default: @@ -1802,358 +1678,198 @@ bool prepareAggregateCount(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate) return true; } -static bool prepareDistinctSet(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate) +static bool prepareDistinctSet(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, int aggIndex) { // we need a hash-set according to the aggregated type (preferably but not necessarily linear-probing hash) // we use an opaque datum type, with a tailor-made hash-function and equals function - AddPrepareDistinctSet(ctx, aggregate->_element_type); + AddPrepareDistinctSet(ctx, aggIndex, aggregate->_element_type); return true; } -bool prepareAggregate(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate) +bool prepareAggregates(JitLlvmCodeGenContext* ctx, JitAggregate* aggregates, int aggCount) { bool result = false; + for (int i = 0; i < aggCount; ++i) { + switch (aggregates[i]._aggreaget_op) { + case JIT_AGGREGATE_AVG: + result = prepareAggregateAvg(ctx, &aggregates[i], i); + break; - switch (aggregate->_aggreaget_op) { - case JIT_AGGREGATE_AVG: - result = prepareAggregateAvg(ctx, aggregate); - break; + case JIT_AGGREGATE_SUM: + result = prepareAggregateSum(ctx, &aggregates[i], i); + break; - case JIT_AGGREGATE_SUM: - result = prepareAggregateSum(ctx, aggregate); - break; + case JIT_AGGREGATE_MAX: + case JIT_AGGREGATE_MIN: + result = prepareAggregateMaxMin(ctx, &aggregates[i], i); + break; - case JIT_AGGREGATE_MAX: - case JIT_AGGREGATE_MIN: - result = prepareAggregateMaxMin(ctx, aggregate); - break; + case JIT_AGGREGATE_COUNT: + result = prepareAggregateCount(ctx, &aggregates[i], i); + break; - case JIT_AGGREGATE_COUNT: - result = prepareAggregateCount(ctx, aggregate); - break; + default: + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "JIT Compile", + "Cannot prepare for aggregation: invalid aggregate operator %d", + (int)aggregates[i]._aggreaget_op); + result = false; + break; + } - default: - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "JIT Compile", - "Cannot prepare for aggregation: invalid aggregate operator %d", - (int)aggregate->_aggreaget_op); - break; - } - - if (result) { - if (aggregate->_distinct) { - result = prepareDistinctSet(ctx, aggregate); + if (result) { + if (aggregates[i]._distinct) { + result = prepareDistinctSet(ctx, &aggregates[i], i); + } } } return result; } +static llvm::Value* BuildAggregateFunc( + JitLlvmCodeGenContext* ctx, Oid funcId, Oid collationId, llvm::Value* currentAggregate, llvm::Value* varExpr) +{ + uint32_t argCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGAggFunctionInfo(funcId, &argCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("BuildAggregateFunc(): Cannot find function by id: %u", (unsigned)funcId); + return nullptr; + } + MOT_ASSERT(argCount == 2); + if (argCount != 2) { + MOT_LOG_TRACE( + "BuildAggregateFunc(): Invalid argument count %u in aggregate function %u", argCount, (unsigned)funcId); + return nullptr; + } + llvm::Value* isNull = JIT_CONST_INT32(0); + // aggregate functions do not require argument types + llvm::Value* result = AddInvokePGFunction2( + ctx, funcPtr, collationId, isStrict, currentAggregate, isNull, InvalidOid, varExpr, isNull, InvalidOid); + return result; +} + static llvm::Value* buildAggregateAvg( JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, llvm::Value* current_aggregate, llvm::Value* var_expr) { - llvm::Value* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8AVGFUNCOID: - // the current_aggregate is a 2 numeric array, and the var expression should evaluate to int8 - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int8_avg_accum, 0); - break; - - case INT4AVGFUNCOID: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int4 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, nullptr)" return true - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int4_avg_accum, 0); - break; - - case INT2AVGFUNCOID: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int2 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, nullptr)" return true - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int2_avg_accum, 0); - break; - - case 5537: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int1 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, nullptr)" return true - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int1_avg_accum, 0); - break; - - case 2104: // float4 - // the current_aggregate is a 3 float8 array, and the var expression should evaluate to float4 - // the function computes 3 values: count, sum, square sum - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, nullptr)" return true - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float4_accum, 0); - break; - - case 2105: // float8 - // the current_aggregate is a 3 float8 array, and the var expression should evaluate to float8 - // the function computes 3 values: count, sum, square sum - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, nullptr)" return true - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float8_accum, 0); - break; - - case NUMERICAVGFUNCOID: - // the current_aggregate is a 2 numeric array, and the var expression should evaluate to numeric - aggregate_expr = - AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_numeric_avg_accum, 0); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate AVG() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; + return BuildAggregateFunc(ctx, aggregate->_func_id, aggregate->m_funcCollationId, current_aggregate, var_expr); } static llvm::Value* buildAggregateSum( JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, llvm::Value* current_aggregate, llvm::Value* var_expr) { - llvm::Value* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8SUMFUNCOID: - // current sum in numeric, and next value is int8, both can be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int8_sum, 0); - break; - - case INT4SUMFUNCOID: - // current aggregate is a int8, and value is int4, both can be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int4_sum, 0); - break; - - case INT2SUMFUNCOID: - // current aggregate is a int8, and value is int3, both can be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int2_sum, 0); - break; - - case 2110: // float4 - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float4pl, 0); - break; - - case 2111: // float8 - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float8pl, 0); - break; - - case NUMERICSUMFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_numeric_add, 0); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate SUM() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; + return BuildAggregateFunc(ctx, aggregate->_func_id, aggregate->m_funcCollationId, current_aggregate, var_expr); } static llvm::Value* buildAggregateMax( JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, llvm::Value* current_aggregate, llvm::Value* var_expr) { - llvm::Value* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8LARGERFUNCOID: - // current aggregate is a int8, and value is int8, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int8larger, 0); - break; - - case INT4LARGERFUNCOID: - // current aggregate is a int4, and value is int4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int4larger, 0); - break; - - case INT2LARGERFUNCOID: - // current aggregate is a int2, and value is int2, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int2larger, 0); - break; - - case 5538: - // current aggregate is a int1, and value is int1, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int1larger, 0); - break; - - case 2119: // float4larger - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float4larger, 0); - break; - - case 2120: // float8larger - // current aggregate is a float8, and value is float8, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float8larger, 0); - break; - - case NUMERICLARGERFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_numeric_larger, 0); - break; - - case 2126: - // current aggregate is a timestamp, and value is timestamp, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_timestamp_larger, 0); - break; - - case 2122: - // current aggregate is a date, and value is date, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_date_larger, 0); - break; - - case 2244: - // current aggregate is a bpchar, and value is bpchar, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_bpchar_larger, 0); - break; - - case 2129: - // current aggregate is a text, and value is text, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_text_larger, 0); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate MAX() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; + return BuildAggregateFunc(ctx, aggregate->_func_id, aggregate->m_funcCollationId, current_aggregate, var_expr); } static llvm::Value* buildAggregateMin( JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, llvm::Value* current_aggregate, llvm::Value* var_expr) { - llvm::Value* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8SMALLERFUNCOID: - // current sum is a int8, and value is int8, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int8smaller, 0); - break; - - case INT4SMALLERFUNCOID: - // current aggregate is a int4, and value is int4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int4smaller, 0); - break; - - case INT2SMALLERFUNCOID: - // current aggregate is a int2, and value is int2, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_int2smaller, 0); - break; - - case 2135: // float4smaller - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float4smaller, 0); - break; - - case 2120: // float8smaller - // current aggregate is a float8, and value is float8, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_float8smaller, 0); - break; - - case NUMERICSMALLERFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_numeric_smaller, 0); - break; - - case 2142: - // current aggregate is a timestamp, and value is timestamp, both can **NOT** be null - aggregate_expr = - AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_timestamp_smaller, 0); - break; - - case 2138: - // current aggregate is a date, and value is date, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_date_smaller, 0); - break; - - case 2245: - // current aggregate is a bpchar, and value is bpchar, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_bpchar_smaller, 0); - break; - - case 2145: - // current aggregate is a text, and value is text, both can **NOT** be null - aggregate_expr = AddExecBinaryOperator(ctx, current_aggregate, var_expr, ctx->_builtin_text_smaller, 0); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate MIN() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; + return BuildAggregateFunc(ctx, aggregate->_func_id, aggregate->m_funcCollationId, current_aggregate, var_expr); } static llvm::Value* buildAggregateCount( JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate, llvm::Value* count_aggregate) { - llvm::Value* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case 2147: // int8inc_any - // current aggregate is int8, and can **NOT** be null - aggregate_expr = AddExecUnaryOperator(ctx, count_aggregate, ctx->_builtin_int8inc, 0); - break; - - case 2803: // int8inc - // current aggregate is int8, and can **NOT** be null - aggregate_expr = AddExecUnaryOperator(ctx, count_aggregate, ctx->_builtin_int8inc, 0); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate COUNT() operator function type: %d", aggregate->_func_id); - break; + uint32_t argCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGAggFunctionInfo(aggregate->_func_id, &argCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("buildAggregateCount(): Cannot find function by id: %u", (unsigned)aggregate->_func_id); + return nullptr; } - return aggregate_expr; + // some aggregate functions use more than 1 argument, which are all dummies, so we just fill nulls. + // see implementation of int8inc_any, + // note: aggregate functions do not require argument types + llvm::Value* result = nullptr; + llvm::Value* isNull = JIT_CONST_INT32(0); + llvm::Value* dummy = JIT_CONST_INT64(0); + if (argCount == 1) { + result = AddInvokePGFunction1( + ctx, funcPtr, aggregate->m_funcCollationId, isStrict, count_aggregate, isNull, InvalidOid); + } else if (argCount == 2) { + result = AddInvokePGFunction2(ctx, + funcPtr, + aggregate->m_funcCollationId, + isStrict, + count_aggregate, + isNull, + InvalidOid, + dummy, + isNull, + InvalidOid); + } else { + MOT_LOG_TRACE("buildAggregateCount(): Invalid argument count %u in aggregate function %u", + argCount, + (unsigned)aggregate->_func_id); + return nullptr; + } + return result; } -static bool buildAggregateMaxMin(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, llvm::Value* var_expr) +static bool buildAggregateMaxMin( + JitLlvmCodeGenContext* ctx, int aggIndex, JitAggregate* aggregate, llvm::Value* var_expr) { bool result = false; llvm::Value* aggregateExpr = nullptr; // we first check if the min/max value is null and if so just store the column value - JIT_IF_BEGIN(test_max_min_value_null) - llvm::Value* res = AddGetAggMaxMinIsNull(ctx); - JIT_IF_EVAL(res) - AddSetAggValue(ctx, var_expr); - AddSetAggMaxMinNotNull(ctx); - JIT_ELSE() - // get the aggregated value and call the operator - llvm::Value* current_aggregate = AddGetAggValue(ctx); - switch (aggregate->_aggreaget_op) { - case JIT_AGGREGATE_MAX: - aggregateExpr = buildAggregateMax(ctx, aggregate, current_aggregate, var_expr); - break; - - case JIT_AGGREGATE_MIN: - aggregateExpr = buildAggregateMin(ctx, aggregate, current_aggregate, var_expr); - break; - - default: - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "JIT Compile", "Invalid aggregate operator %d", (int)aggregate->_aggreaget_op); - break; + JIT_IF_BEGIN(test_max_min_value_null); + llvm::Value* res = AddGetAggValueIsNull(ctx, aggIndex); + JIT_IF_EVAL(res); + { + AddSetAggValue(ctx, aggIndex, var_expr); } - JIT_IF_END() + JIT_ELSE(); + { + // get the aggregated value and call the operator + llvm::Value* current_aggregate = AddGetAggValue(ctx, aggIndex); + switch (aggregate->_aggreaget_op) { + case JIT_AGGREGATE_MAX: + aggregateExpr = buildAggregateMax(ctx, aggregate, current_aggregate, var_expr); + break; - if (aggregateExpr != nullptr) { - AddSetAggValue(ctx, aggregateExpr); - result = true; + case JIT_AGGREGATE_MIN: + aggregateExpr = buildAggregateMin(ctx, aggregate, current_aggregate, var_expr); + break; + + default: + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "JIT Compile", "Invalid aggregate operator %d", (int)aggregate->_aggreaget_op); + break; + } + + if (aggregateExpr != nullptr) { + AddSetAggValue(ctx, aggIndex, aggregateExpr); + result = true; + } } + JIT_IF_END(); return result; } -static bool buildAggregateTuple(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, llvm::Value* var_expr) +static bool buildAggregateTuple( + JitLlvmCodeGenContext* ctx, int aggIndex, JitAggregate* aggregate, llvm::Value* var_expr) { bool result = false; if ((aggregate->_aggreaget_op == JIT_AGGREGATE_MAX) || (aggregate->_aggreaget_op == JIT_AGGREGATE_MIN)) { - result = buildAggregateMaxMin(ctx, aggregate, var_expr); + result = buildAggregateMaxMin(ctx, aggIndex, aggregate, var_expr); } else { // get the aggregated value llvm::Value* current_aggregate = nullptr; if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - current_aggregate = AddLoadAvgArray(ctx); + current_aggregate = AddLoadAvgArray(ctx, aggIndex); } else { - current_aggregate = AddGetAggValue(ctx); + current_aggregate = AddGetAggValue(ctx, aggIndex); } // the operators below take care of null inputs @@ -2180,9 +1896,9 @@ static bool buildAggregateTuple(JitLlvmCodeGenContext* ctx, JitAggregate* aggreg // write back the sum to the aggregated value/array if (aggregate_expr != nullptr) { if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - AddSaveAvgArray(ctx, aggregate_expr); + AddSaveAvgArray(ctx, aggIndex, aggregate_expr); } else { - AddSetAggValue(ctx, aggregate_expr); + AddSetAggValue(ctx, aggIndex, aggregate_expr); } result = true; } else { @@ -2193,54 +1909,94 @@ static bool buildAggregateTuple(JitLlvmCodeGenContext* ctx, JitAggregate* aggreg return result; } -bool buildAggregateRow( - JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, llvm::Value* row, llvm::BasicBlock* next_block) +static inline bool BuildAggreagateValue(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, int aggIndex, + llvm::Value* value, llvm::Value* isNull, llvm::Value* aggCount) +{ + bool result = true; + JIT_IF_BEGIN(agg_value_is_null); + JIT_IF_EVAL(isNull); + { + IssueDebugLog("Ignoring null value in aggregate"); + } + JIT_ELSE(); + { + IssueDebugLog("Aggregating value"); + result = buildAggregateTuple(ctx, aggIndex, aggregate, value); + if (result) { + // update number of aggregates processed for this row + llvm::Value* aggValue = ctx->m_builder->CreateLoad(aggCount, true); + llvm::Value* newValue = ctx->m_builder->CreateAdd(aggValue, JIT_CONST_INT32(1)); + ctx->m_builder->CreateStore(newValue, aggCount, true); + } + } + JIT_IF_END(); + return result; +} + +bool buildAggregateRow(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, int aggIndex, llvm::Value* row, + llvm::Value* innerRow, llvm::Value* aggCount) { bool result = false; // extract the aggregated column - llvm::Value* table = (aggregate->_table == ctx->_table_info.m_table) ? ctx->table_value : ctx->inner_table_value; - llvm::Value* expr = AddReadDatumColumn(ctx, table, row, aggregate->_table_column_id, 0); + llvm::Value* expr = nullptr; + llvm::Value* exprIsNull = nullptr; + if (aggregate->_table != nullptr) { + llvm::Value* table = + (aggregate->_table == ctx->_table_info.m_table) ? ctx->table_value : ctx->inner_table_value; + llvm::Value* usedRow = (aggregate->_table == ctx->_table_info.m_table) ? row : innerRow; + int isInnerRow = (usedRow == innerRow) ? 1 : 0; + int subQueryIndex = -1; + expr = AddReadDatumColumn(ctx, table, usedRow, aggregate->_table_column_id, isInnerRow, subQueryIndex); + exprIsNull = AddGetExprIsNull(ctx); + } else { + expr = llvm::ConstantInt::get(ctx->DATUM_T, Int64GetDatum(1), true); + exprIsNull = llvm::ConstantInt::get(ctx->DATUM_T, BoolGetDatum(false), true); + } // we first check if we have DISTINCT modifier if (aggregate->_distinct) { // check row is distinct in state set (managed on the current jit context) // emit code to convert column to primitive data type, add value to distinct set and verify // if value is not distinct then do not use this row in aggregation - JIT_IF_BEGIN(test_distinct_value) - llvm::Value* res = AddInsertDistinctItem(ctx, aggregate->_element_type, expr); - JIT_IF_EVAL_NOT(res) - IssueDebugLog("Value is not distinct, skipping aggregated row"); - ctx->_builder->CreateBr(next_block); - JIT_IF_END() - } - - // aggregate row - IssueDebugLog("Aggregating row into inner value"); - result = buildAggregateTuple(ctx, aggregate, expr); - if (result) { - // update number of rows processes - buildIncrementRowsProcessed(ctx); + JIT_IF_BEGIN(test_distinct_value); + llvm::Value* res = AddInsertDistinctItem(ctx, aggIndex, aggregate->_element_type, expr); + JIT_IF_EVAL_NOT(res); + { + IssueDebugLog("Value is not distinct, skipping aggregated row"); + } + JIT_ELSE(); + { + IssueDebugLog("Aggregating distinct value"); + result = BuildAggreagateValue(ctx, aggregate, aggIndex, expr, exprIsNull, aggCount); + } + JIT_IF_END(); + } else { + result = BuildAggreagateValue(ctx, aggregate, aggIndex, expr, exprIsNull, aggCount); } return result; } -void buildAggregateResult(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate) +void buildAggregateResult(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregates, int aggCount) { // in case of average we compute it when aggregate loop is done - if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - llvm::Value* avg_value = AddComputeAvgFromArray( - ctx, aggregate->_avg_element_type); // we infer this during agg op analysis, but don't save it... - AddWriteTupleDatum(ctx, 0, avg_value); // we alway aggregate to slot tuple 0 - } else { - llvm::Value* count_value = AddGetAggValue(ctx); - AddWriteTupleDatum(ctx, 0, count_value); // we alway aggregate to slot tuple 0 - } + for (int aggIndex = 0; aggIndex < aggCount; ++aggIndex) { + const JitAggregate* aggregate = &aggregates[aggIndex]; - // we take the opportunity to cleanup as well - if (aggregate->_distinct) { - AddDestroyDistinctSet(ctx, aggregate->_element_type); + llvm::Value* aggIsNull = AddGetAggValueIsNull(ctx, aggIndex); + if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { + llvm::Value* avg_value = AddComputeAvgFromArray(ctx, aggIndex, aggregate->_avg_element_type); + AddWriteTupleDatum(ctx, aggIndex, avg_value, aggIsNull); + } else { + llvm::Value* count_value = AddGetAggValue(ctx, aggIndex); + AddWriteTupleDatum(ctx, aggIndex, count_value, aggIsNull); + } + + // we take the opportunity to cleanup as well + if (aggregate->_distinct) { + AddDestroyDistinctSet(ctx, aggIndex, aggregate->_element_type); + } } } @@ -2249,33 +2005,103 @@ void buildCheckLimit(JitLlvmCodeGenContext* ctx, int limit_count) // if a limit clause exists, then increment limit counter and check if reached limit if (limit_count > 0) { AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) + JIT_IF_BEGIN(limit_count_reached); llvm::Value* current_limit_count = AddGetStateLimitCounter(ctx); - llvm::Value* limit_count_inst = JIT_CONST(limit_count); + llvm::Value* limit_count_inst = JIT_CONST_INT32(limit_count); JIT_IF_EVAL_CMP(current_limit_count, limit_count_inst, JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - // cleanup and signal scan ended (it is safe to cleanup even if not initialized, so we cleanup everything) - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); - AddSetScanEnded(ctx, 1); - JIT_IF_END() + { + IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); + // cleanup and signal scan ended (it is safe to cleanup even if not initialized, so we cleanup everything) + AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); + AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); + AddSetScanEnded(ctx, 1); + } + JIT_IF_END(); } } -bool selectJoinRows( - JitLlvmCodeGenContext* ctx, llvm::Value* outer_row_copy, llvm::Value* inner_row, JitJoinPlan* plan, int* max_arg) +void BuildCheckLimitNoState(JitLlvmCodeGenContext* ctx, int limitCount) { - // select inner and outer row expressions into result tuple (no aggregate because aggregate is not stateful) - IssueDebugLog("Retrieved row from state iterator, beginning to select columns into result tuple"); - if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Inner Point JOIN query: failed to select outer row expressions"); + if (limitCount > 0) { + JIT_IF_BEGIN(limit_count_reached); + llvm::Value* rowCount = ctx->m_builder->CreateLoad(ctx->rows_processed, true); + llvm::Value* limitCountValue = JIT_CONST_INT32(limitCount); + JIT_IF_EVAL_CMP(rowCount, limitCountValue, JIT_ICMP_EQ); + { + IssueDebugLog("Reached limit specified in limit clause, range scan ended"); + JIT_WHILE_BREAK(); + } + JIT_IF_END(); + } +} + +bool BuildSelectLeftJoinRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* outerRowCopy, JitJoinPlan* plan, + llvm::Value* innerRow, llvm::Value* filterPassed) +{ + JIT_IF_BEGIN(filters_passed); + JIT_IF_EVAL_NOT(ctx->m_builder->CreateLoad(ctx->INT32_T, filterPassed)); + { + IssueDebugLog("Selecting null column values for LEFT JOIN"); + llvm::Value* nullRow = + ctx->m_builder->CreateCast(llvm::Instruction::IntToPtr, JIT_CONST_INT64(0), ctx->RowType->getPointerTo()); + if (!selectRowColumns(ctx, outerRowCopy, &plan->_select_exprs, nullRow)) { + MOT_LOG_TRACE( + "Failed to generate jitted code for Point JOIN query: failed to select row columns into result tuple"); + return false; + } + } + JIT_ELSE(); + { + // now begin selecting columns into result + MOT_LOG_TRACE("Selecting row columns in the result tuple for LEFT JOIN"); + if (!selectRowColumns(ctx, outerRowCopy, &plan->_select_exprs, innerRow)) { + MOT_LOG_TRACE( + "Failed to generate jitted code for Point JOIN query: failed to select row columns into result tuple"); + return false; + } + } + JIT_IF_END(); + + return true; +} + +static bool BuildOneTimeFilter(JitLlvmCodeGenContext* ctx, JitExpr* filter, llvm::BasicBlock* nextBlock) +{ + llvm::Value* filterExpr = ProcessExpr(ctx, nullptr, nullptr, filter); + if (filterExpr == nullptr) { + MOT_LOG_TRACE("BuildOneTimeFilter(): Failed to process filter expression"); return false; } - if (!selectRowColumns(ctx, inner_row, &plan->_select_exprs, max_arg, JIT_RANGE_SCAN_INNER)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Inner Point JOIN query: failed to select outer row expressions"); - return false; + + JIT_IF_BEGIN(filter_row); + IssueDebugLog("Checking if one-time filter passes"); + JIT_IF_EVAL_NOT(filterExpr); + { + IssueDebugLog("One-time filter did not pass"); + if (nextBlock != nullptr) { + JIT_GOTO(nextBlock); + } else { + JIT_PROFILE_END(ctx); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_LOCAL_ROW_NOT_FOUND)); + } + } + JIT_ELSE(); + { + IssueDebugLog("One-time filter passed"); + } + JIT_IF_END(); + return true; +} + +bool BuildOneTimeFilters( + JitLlvmCodeGenContext* ctx, JitFilterArray* oneTimeFilterArray, llvm::BasicBlock* nextBlock /* = nullptr */) +{ + MOT_LOG_TRACE("Building %d one-time filters", oneTimeFilterArray->_filter_count); + for (int i = 0; i < oneTimeFilterArray->_filter_count; ++i) { + if (!BuildOneTimeFilter(ctx, oneTimeFilterArray->_scan_filters[i], nextBlock)) { + MOT_LOG_TRACE("Failed to build one-time filter %d", i); + return false; + } } return true; } diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.h index 8bdb79505..a749a6f3c 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_blocks.h @@ -56,8 +56,8 @@ llvm::Value* buildCreateNewRow(JitLlvmCodeGenContext* ctx); llvm::Value* buildSearchRow( JitLlvmCodeGenContext* ctx, MOT::AccessType access_type, JitRangeScanType range_scan_type, int subQueryIndex = -1); -bool buildFilterRow( - JitLlvmCodeGenContext* ctx, llvm::Value* row, JitFilterArray* filters, int* max_arg, llvm::BasicBlock* next_block); +bool buildFilterRow(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitFilterArray* filters, + llvm::BasicBlock* next_block); /** @brief Adds code to insert a new row. */ void buildInsertRow(JitLlvmCodeGenContext* ctx, llvm::Value* row); @@ -65,39 +65,49 @@ void buildInsertRow(JitLlvmCodeGenContext* ctx, llvm::Value* row); /** @brief Adds code to delete a row. */ void buildDeleteRow(JitLlvmCodeGenContext* ctx); -/** @brief Adds code to get row from iterator. */ -llvm::Value* buildGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* endLoopBlock, - MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, JitLlvmRuntimeCursor* cursor, - JitRangeScanType range_scan_type, int subQueryIndex = -1); - -bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, int* maxArg, - JitRangeScanType rangeScanType, llvm::Value* outerRow, int exprCount = -1, int subQueryIndex = -1); - -bool writeRowColumns( - JitLlvmCodeGenContext* ctx, llvm::Value* row, JitColumnExprArray* expr_array, int* max_arg, bool is_update); - -bool selectRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitSelectExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, int subQueryIndex = -1); - -llvm::Value* buildPrepareStateScanRow(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, - JitRangeScanType range_scan_type, MOT::AccessType access_mode, int* max_arg, llvm::Value* outer_row, - llvm::BasicBlock* next_block, llvm::BasicBlock** loop_block); - -JitLlvmRuntimeCursor buildRangeCursor(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitIndexScanDirection indexScanDirection, llvm::Value* outerRow, +/** @brief Adds code to check whether cursor contains one more row (without row copy). */ +void BuildCheckRowExistsInIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* endLoopBlock, + JitIndexScanDirection indexScanDirection, JitLlvmRuntimeCursor* cursor, JitRangeScanType rangeScanType, int subQueryIndex = -1); -bool prepareAggregate(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate); +/** @brief Adds code to get row from iterator. */ +llvm::Value* buildGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::BasicBlock* startLoopBlock, + llvm::BasicBlock* endLoopBlock, MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, + JitLlvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, int subQueryIndex = -1); -bool buildAggregateRow( - JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, llvm::Value* row, llvm::BasicBlock* next_block); +bool buildPointScan(JitLlvmCodeGenContext* ctx, JitColumnExprArray* exprArray, JitRangeScanType rangeScanType, + llvm::Value* outerRow, int exprCount = -1, int subQueryIndex = -1); -void buildAggregateResult(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregate); +bool writeRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitColumnExprArray* expr_array, bool is_update); + +bool selectRowColumns( + JitLlvmCodeGenContext* ctx, llvm::Value* row, JitSelectExprArray* expr_array, llvm::Value* innerRow = nullptr); + +llvm::Value* buildPrepareStateScanRow(JitLlvmCodeGenContext* ctx, JitIndexScan* index_scan, + JitRangeScanType range_scan_type, MOT::AccessType access_mode, llvm::Value* outer_row, llvm::BasicBlock* next_block, + llvm::BasicBlock** loop_block, bool emitReturnOnFail = true); + +JitLlvmRuntimeCursor buildRangeCursor(JitLlvmCodeGenContext* ctx, JitIndexScan* indexScan, + JitRangeScanType rangeScanType, llvm::Value* outerRow, int subQueryIndex = -1); + +bool prepareAggregates(JitLlvmCodeGenContext* ctx, JitAggregate* aggregates, int aggCount); + +bool buildAggregateRow(JitLlvmCodeGenContext* ctx, JitAggregate* aggregate, int aggIndex, llvm::Value* row, + llvm::Value* innerRow, llvm::Value* aggCount); + +void buildAggregateResult(JitLlvmCodeGenContext* ctx, const JitAggregate* aggregates, int aggCount); void buildCheckLimit(JitLlvmCodeGenContext* ctx, int limit_count); -bool selectJoinRows( - JitLlvmCodeGenContext* ctx, llvm::Value* outer_row_copy, llvm::Value* inner_row, JitJoinPlan* plan, int* max_arg); +void BuildCheckLimitNoState(JitLlvmCodeGenContext* ctx, int limitCount); + +llvm::Value* ProcessExpr(JitLlvmCodeGenContext* ctx, llvm::Value* row, llvm::Value* innerRow, JitExpr* expr); + +bool BuildSelectLeftJoinRowColumns(JitLlvmCodeGenContext* ctx, llvm::Value* outerRowCopy, JitJoinPlan* plan, + llvm::Value* innerRow, llvm::Value* filterPassed); + +bool BuildOneTimeFilters( + JitLlvmCodeGenContext* ctx, JitFilterArray* oneTimeFilterArray, llvm::BasicBlock* nextBlock = nullptr); } // namespace JitExec #endif /* JIT_LLVM_BLOCKS_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_funcs.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_funcs.h index 7b272af90..5da1fad1c 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_funcs.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_funcs.h @@ -31,29 +31,31 @@ */ #include "jit_llvm_query.h" #include "jit_plan.h" +#include "jit_llvm_util.h" +#include "jit_profiler.h" #include namespace JitExec { /** @brief Gets a key from the execution context. */ -inline llvm::Value* getExecContextKey(JitLlvmCodeGenContext* ctx, JitRangeIteratorType range_itr_type, - JitRangeScanType range_scan_type, int subQueryIndex) +inline llvm::Value* getExecContextKey( + JitLlvmCodeGenContext* ctx, JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType, int subQueryIndex) { llvm::Value* key = nullptr; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { + if (rangeScanType == JIT_RANGE_SCAN_INNER) { + if (rangeItrType == JIT_RANGE_ITERATOR_END) { key = ctx->inner_end_iterator_key_value; } else { key = ctx->inner_key_value; } - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { + } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { + if (rangeItrType == JIT_RANGE_ITERATOR_END) { key = ctx->end_iterator_key_value; } else { key = ctx->key_value; } - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { + } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { + if (rangeItrType == JIT_RANGE_ITERATOR_END) { key = ctx->m_subQueryData[subQueryIndex].m_endIteratorKey; } else { key = ctx->m_subQueryData[subQueryIndex].m_searchKey; @@ -62,6 +64,19 @@ inline llvm::Value* getExecContextKey(JitLlvmCodeGenContext* ctx, JitRangeIterat return key; } +inline llvm::Value* getExecContextTable(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) +{ + llvm::Value* tableValue = nullptr; + if (rangeScanType == JIT_RANGE_SCAN_INNER) { + tableValue = ctx->inner_table_value; + } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { + tableValue = ctx->table_value; + } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { + tableValue = ctx->m_subQueryData[subQueryIndex].m_table; + } + return tableValue; +} + inline llvm::Value* getExecContextIndex(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) { llvm::Value* indexValue = nullptr; @@ -76,93 +91,84 @@ inline llvm::Value* getExecContextIndex(JitLlvmCodeGenContext* ctx, JitRangeScan } /*--------------------------- Define LLVM Helper Prototypes ---------------------------*/ -inline llvm::FunctionCallee defineFunction(llvm::Module* module, llvm::Type* ret_type, const char* name, ...) -{ - va_list vargs; - va_start(vargs, name); - std::vector args; - llvm::Type* arg_type = va_arg(vargs, llvm::Type*); - while (arg_type != nullptr) { - args.push_back(arg_type); - arg_type = va_arg(vargs, llvm::Type*); - } - va_end(vargs); - llvm::ArrayRef argsRef(args); - llvm::FunctionType* funcType = llvm::FunctionType::get(ret_type, argsRef, false); - return module->getOrInsertFunction(name, funcType); -} - inline void defineDebugLog(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->debugLogFunc = defineFunction(module, ctx->VOID_T, "debugLog", ctx->STR_T, ctx->STR_T, nullptr); + ctx->debugLogFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "debugLog", ctx->STR_T, ctx->STR_T, nullptr); } inline void defineIsSoftMemoryLimitReached(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->isSoftMemoryLimitReachedFunc = defineFunction(module, ctx->INT32_T, "isSoftMemoryLimitReached", nullptr); + ctx->isSoftMemoryLimitReachedFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "isSoftMemoryLimitReached", nullptr); } inline void defineGetPrimaryIndex(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getPrimaryIndexFunc = defineFunction( + ctx->getPrimaryIndexFunc = llvm_util::DefineFunction( module, ctx->IndexType->getPointerTo(), "getPrimaryIndex", ctx->TableType->getPointerTo(), nullptr); } inline void defineGetTableIndex(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getTableIndexFunc = defineFunction( + ctx->getTableIndexFunc = llvm_util::DefineFunction( module, ctx->IndexType->getPointerTo(), "getTableIndex", ctx->TableType->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineInitKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->initKeyFunc = defineFunction( + ctx->initKeyFunc = llvm_util::DefineFunction( module, ctx->VOID_T, "InitKey", ctx->KeyType->getPointerTo(), ctx->IndexType->getPointerTo(), nullptr); } inline void defineGetColumnAt(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getColumnAtFunc = defineFunction( + ctx->getColumnAtFunc = llvm_util::DefineFunction( module, ctx->ColumnType->getPointerTo(), "getColumnAt", ctx->TableType->getPointerTo(), ctx->INT32_T, nullptr); } -inline void defineSetExprArgIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) +inline void DefineGetExprIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setExprArgIsNullFunc = - defineFunction(module, ctx->VOID_T, "setExprArgIsNull", ctx->INT32_T, ctx->INT32_T, nullptr); + ctx->m_getExprIsNullFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "GetExprIsNull", nullptr); } -inline void defineGetExprArgIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) +inline void DefineSetExprIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getExprArgIsNullFunc = defineFunction(module, ctx->INT32_T, "getExprArgIsNull", ctx->INT32_T, nullptr); + ctx->m_setExprIsNullFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "SetExprIsNull", ctx->INT32_T, nullptr); +} + +inline void DefineGetExprCollation(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getExprCollationFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "GetExprCollation", nullptr); +} + +inline void DefineSetExprCollation(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setExprCollationFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "SetExprCollation", ctx->INT32_T, nullptr); } inline void defineGetDatumParam(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getDatumParamFunc = defineFunction(module, - ctx->DATUM_T, - "getDatumParam", - ctx->ParamListInfoDataType->getPointerTo(), - ctx->INT32_T, - ctx->INT32_T, - nullptr); + ctx->getDatumParamFunc = llvm_util::DefineFunction( + module, ctx->DATUM_T, "getDatumParam", ctx->ParamListInfoDataType->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineReadDatumColumn(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->readDatumColumnFunc = defineFunction(module, + ctx->readDatumColumnFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "readDatumColumn", ctx->TableType->getPointerTo(), ctx->RowType->getPointerTo(), ctx->INT32_T, ctx->INT32_T, + ctx->INT32_T, nullptr); } inline void defineWriteDatumColumn(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->writeDatumColumnFunc = defineFunction(module, + ctx->writeDatumColumnFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "writeDatumColumn", ctx->RowType->getPointerTo(), @@ -173,7 +179,7 @@ inline void defineWriteDatumColumn(JitLlvmCodeGenContext* ctx, llvm::Module* mod inline void defineBuildDatumKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->buildDatumKeyFunc = defineFunction(module, + ctx->buildDatumKeyFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "buildDatumKey", ctx->ColumnType->getPointerTo(), @@ -188,65 +194,67 @@ inline void defineBuildDatumKey(JitLlvmCodeGenContext* ctx, llvm::Module* module inline void defineSetBit(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setBitFunc = - defineFunction(module, ctx->VOID_T, "setBit", ctx->BitmapSetType->getPointerTo(), ctx->INT32_T, nullptr); + ctx->setBitFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setBit", ctx->BitmapSetType->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineResetBitmapSet(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->resetBitmapSetFunc = - defineFunction(module, ctx->VOID_T, "resetBitmapSet", ctx->BitmapSetType->getPointerTo(), nullptr); + llvm_util::DefineFunction(module, ctx->VOID_T, "resetBitmapSet", ctx->BitmapSetType->getPointerTo(), nullptr); } inline void defineGetTableFieldCount(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->getTableFieldCountFunc = - defineFunction(module, ctx->INT32_T, "getTableFieldCount", ctx->TableType->getPointerTo(), nullptr); + llvm_util::DefineFunction(module, ctx->INT32_T, "getTableFieldCount", ctx->TableType->getPointerTo(), nullptr); } inline void defineWriteRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->writeRowFunc = defineFunction( + ctx->writeRowFunc = llvm_util::DefineFunction( module, ctx->INT32_T, "writeRow", ctx->RowType->getPointerTo(), ctx->BitmapSetType->getPointerTo(), nullptr); } inline void defineSearchRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->searchRowFunc = defineFunction(module, + ctx->searchRowFunc = llvm_util::DefineFunction(module, ctx->RowType->getPointerTo(), "searchRow", ctx->TableType->getPointerTo(), ctx->KeyType->getPointerTo(), ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, nullptr); } inline void defineCreateNewRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->createNewRowFunc = - defineFunction(module, ctx->RowType->getPointerTo(), "createNewRow", ctx->TableType->getPointerTo(), nullptr); + ctx->createNewRowFunc = llvm_util::DefineFunction( + module, ctx->RowType->getPointerTo(), "createNewRow", ctx->TableType->getPointerTo(), nullptr); } inline void defineInsertRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->insertRowFunc = defineFunction( + ctx->insertRowFunc = llvm_util::DefineFunction( module, ctx->INT32_T, "insertRow", ctx->TableType->getPointerTo(), ctx->RowType->getPointerTo(), nullptr); } inline void defineDeleteRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->deleteRowFunc = defineFunction(module, ctx->INT32_T, "deleteRow", nullptr); + ctx->deleteRowFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "deleteRow", nullptr); } inline void defineSetRowNullBits(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setRowNullBitsFunc = defineFunction( + ctx->setRowNullBitsFunc = llvm_util::DefineFunction( module, ctx->VOID_T, "setRowNullBits", ctx->TableType->getPointerTo(), ctx->RowType->getPointerTo(), nullptr); } inline void defineSetExprResultNullBit(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setExprResultNullBitFunc = defineFunction(module, + ctx->setExprResultNullBitFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "setExprResultNullBit", ctx->TableType->getPointerTo(), @@ -257,19 +265,19 @@ inline void defineSetExprResultNullBit(JitLlvmCodeGenContext* ctx, llvm::Module* inline void defineExecClearTuple(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->execClearTupleFunc = - defineFunction(module, ctx->VOID_T, "execClearTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); + ctx->execClearTupleFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "execClearTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); } inline void defineExecStoreVirtualTuple(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->execStoreVirtualTupleFunc = - defineFunction(module, ctx->VOID_T, "execStoreVirtualTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); + ctx->execStoreVirtualTupleFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "execStoreVirtualTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); } inline void defineSelectColumn(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->selectColumnFunc = defineFunction(module, + ctx->selectColumnFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "selectColumn", ctx->TableType->getPointerTo(), @@ -282,19 +290,19 @@ inline void defineSelectColumn(JitLlvmCodeGenContext* ctx, llvm::Module* module) inline void defineSetTpProcessed(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setTpProcessedFunc = - defineFunction(module, ctx->VOID_T, "setTpProcessed", ctx->INT64_T->getPointerTo(), ctx->INT64_T, nullptr); + ctx->setTpProcessedFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setTpProcessed", ctx->INT64_T->getPointerTo(), ctx->INT64_T, nullptr); } inline void defineSetScanEnded(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setScanEndedFunc = - defineFunction(module, ctx->VOID_T, "setScanEnded", ctx->INT32_T->getPointerTo(), ctx->INT32_T, nullptr); + ctx->setScanEndedFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setScanEnded", ctx->INT32_T->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineCopyKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->copyKeyFunc = defineFunction(module, + ctx->copyKeyFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "copyKey", ctx->IndexType->getPointerTo(), @@ -305,7 +313,7 @@ inline void defineCopyKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) inline void defineFillKeyPattern(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->fillKeyPatternFunc = defineFunction(module, + ctx->fillKeyPatternFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "FillKeyPattern", ctx->KeyType->getPointerTo(), @@ -317,7 +325,7 @@ inline void defineFillKeyPattern(JitLlvmCodeGenContext* ctx, llvm::Module* modul inline void defineAdjustKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->adjustKeyFunc = defineFunction(module, + ctx->adjustKeyFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "adjustKey", ctx->KeyType->getPointerTo(), @@ -328,7 +336,7 @@ inline void defineAdjustKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) inline void defineSearchIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->searchIteratorFunc = defineFunction(module, + ctx->searchIteratorFunc = llvm_util::DefineFunction(module, ctx->IndexIteratorType->getPointerTo(), "searchIterator", ctx->IndexType->getPointerTo(), @@ -340,13 +348,13 @@ inline void defineSearchIterator(JitLlvmCodeGenContext* ctx, llvm::Module* modul inline void defineBeginIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->beginIteratorFunc = defineFunction( + ctx->beginIteratorFunc = llvm_util::DefineFunction( module, ctx->IndexIteratorType->getPointerTo(), "beginIterator", ctx->IndexType->getPointerTo(), nullptr); } inline void defineCreateEndIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->createEndIteratorFunc = defineFunction(module, + ctx->createEndIteratorFunc = llvm_util::DefineFunction(module, ctx->IndexIteratorType->getPointerTo(), "createEndIterator", ctx->IndexType->getPointerTo(), @@ -358,7 +366,7 @@ inline void defineCreateEndIterator(JitLlvmCodeGenContext* ctx, llvm::Module* mo inline void defineIsScanEnd(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->isScanEndFunc = defineFunction(module, + ctx->isScanEndFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "isScanEnd", ctx->IndexType->getPointerTo(), @@ -368,9 +376,21 @@ inline void defineIsScanEnd(JitLlvmCodeGenContext* ctx, llvm::Module* module) nullptr); } +inline void DefineCheckRowExistsInIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->CheckRowExistsInIteratorFunc = llvm_util::DefineFunction(module, + ctx->INT32_T, + "CheckRowExistsInIterator", + ctx->IndexType->getPointerTo(), + ctx->IndexIteratorType->getPointerTo(), + ctx->IndexIteratorType->getPointerTo(), + ctx->INT32_T, + nullptr); +} + inline void defineGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getRowFromIteratorFunc = defineFunction(module, + ctx->getRowFromIteratorFunc = llvm_util::DefineFunction(module, ctx->RowType->getPointerTo(), "getRowFromIterator", ctx->IndexType->getPointerTo(), @@ -378,18 +398,20 @@ inline void defineGetRowFromIterator(JitLlvmCodeGenContext* ctx, llvm::Module* m ctx->IndexIteratorType->getPointerTo(), ctx->INT32_T, ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, nullptr); } inline void defineDestroyIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->destroyIteratorFunc = - defineFunction(module, ctx->VOID_T, "destroyIterator", ctx->IndexIteratorType->getPointerTo(), nullptr); + ctx->destroyIteratorFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "destroyIterator", ctx->IndexIteratorType->getPointerTo(), nullptr); } inline void defineSetStateIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setStateIteratorFunc = defineFunction(module, + ctx->setStateIteratorFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "setStateIterator", ctx->IndexIteratorType->getPointerTo(), @@ -400,25 +422,25 @@ inline void defineSetStateIterator(JitLlvmCodeGenContext* ctx, llvm::Module* mod inline void defineGetStateIterator(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getStateIteratorFunc = defineFunction( + ctx->getStateIteratorFunc = llvm_util::DefineFunction( module, ctx->IndexIteratorType->getPointerTo(), "getStateIterator", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineIsStateIteratorNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->isStateIteratorNullFunc = - defineFunction(module, ctx->INT32_T, "isStateIteratorNull", ctx->INT32_T, ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->INT32_T, "isStateIteratorNull", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineIsStateScanEnd(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->isStateScanEndFunc = - defineFunction(module, ctx->INT32_T, "isStateScanEnd", ctx->INT32_T, ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->INT32_T, "isStateScanEnd", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineGetRowFromStateIteratorFunc(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getRowFromStateIteratorFunc = defineFunction(module, + ctx->getRowFromStateIteratorFunc = llvm_util::DefineFunction(module, ctx->RowType->getPointerTo(), "getRowFromStateIterator", ctx->INT32_T, @@ -430,137 +452,143 @@ inline void defineGetRowFromStateIteratorFunc(JitLlvmCodeGenContext* ctx, llvm:: inline void defineDestroyStateIterators(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->destroyStateIteratorsFunc = - defineFunction(module, ctx->VOID_T, "destroyStateIterators", ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->VOID_T, "destroyStateIterators", ctx->INT32_T, nullptr); } inline void defineSetStateScanEndFlag(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->setStateScanEndFlagFunc = - defineFunction(module, ctx->VOID_T, "setStateScanEndFlag", ctx->INT32_T, ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->VOID_T, "setStateScanEndFlag", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineGetStateScanEndFlag(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getStateScanEndFlagFunc = defineFunction(module, ctx->INT32_T, "getStateScanEndFlag", ctx->INT32_T, nullptr); + ctx->getStateScanEndFlagFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "getStateScanEndFlag", ctx->INT32_T, nullptr); } inline void defineResetStateRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->resetStateRowFunc = defineFunction(module, ctx->VOID_T, "resetStateRow", ctx->INT32_T, nullptr); + ctx->resetStateRowFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "resetStateRow", ctx->INT32_T, nullptr); } inline void defineSetStateRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setStateRowFunc = - defineFunction(module, ctx->VOID_T, "setStateRow", ctx->RowType->getPointerTo(), ctx->INT32_T, nullptr); + ctx->setStateRowFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setStateRow", ctx->RowType->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineGetStateRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getStateRowFunc = defineFunction(module, ctx->RowType->getPointerTo(), "getStateRow", ctx->INT32_T, nullptr); + ctx->getStateRowFunc = + llvm_util::DefineFunction(module, ctx->RowType->getPointerTo(), "getStateRow", ctx->INT32_T, nullptr); } inline void defineCopyOuterStateRow(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->copyOuterStateRowFunc = defineFunction(module, ctx->VOID_T, "copyOuterStateRow", nullptr); + ctx->copyOuterStateRowFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "copyOuterStateRow", nullptr); } inline void defineGetOuterStateRowCopy(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->getOuterStateRowCopyFunc = - defineFunction(module, ctx->RowType->getPointerTo(), "getOuterStateRowCopy", nullptr); + llvm_util::DefineFunction(module, ctx->RowType->getPointerTo(), "getOuterStateRowCopy", nullptr); } inline void defineIsStateRowNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->isStateRowNullFunc = defineFunction(module, ctx->INT32_T, "isStateRowNull", ctx->INT32_T, nullptr); + ctx->isStateRowNullFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "isStateRowNull", ctx->INT32_T, nullptr); } inline void defineResetStateLimitCounter(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->resetStateLimitCounterFunc = defineFunction(module, ctx->VOID_T, "resetStateLimitCounter", nullptr); + ctx->resetStateLimitCounterFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "resetStateLimitCounter", nullptr); } inline void defineIncrementStateLimitCounter(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->incrementStateLimitCounterFunc = defineFunction(module, ctx->VOID_T, "incrementStateLimitCounter", nullptr); + ctx->incrementStateLimitCounterFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "incrementStateLimitCounter", nullptr); } inline void defineGetStateLimitCounter(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getStateLimitCounterFunc = defineFunction(module, ctx->INT32_T, "getStateLimitCounter", nullptr); + ctx->getStateLimitCounterFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "getStateLimitCounter", nullptr); } inline void definePrepareAvgArray(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->prepareAvgArrayFunc = - defineFunction(module, ctx->VOID_T, "prepareAvgArray", ctx->INT32_T, ctx->INT32_T, nullptr); + ctx->prepareAvgArrayFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "prepareAvgArray", ctx->INT32_T, ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineLoadAvgArray(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->loadAvgArrayFunc = defineFunction(module, ctx->DATUM_T, "loadAvgArray", nullptr); + ctx->loadAvgArrayFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "loadAvgArray", ctx->INT32_T, nullptr); } inline void defineSaveAvgArray(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->saveAvgArrayFunc = defineFunction(module, ctx->VOID_T, "saveAvgArray", ctx->DATUM_T, nullptr); + ctx->saveAvgArrayFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "saveAvgArray", ctx->INT32_T, ctx->DATUM_T, nullptr); } inline void defineComputeAvgFromArray(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->computeAvgFromArrayFunc = defineFunction(module, ctx->DATUM_T, "computeAvgFromArray", ctx->INT32_T, nullptr); + ctx->computeAvgFromArrayFunc = + llvm_util::DefineFunction(module, ctx->DATUM_T, "computeAvgFromArray", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineResetAggValue(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->resetAggValueFunc = defineFunction(module, ctx->VOID_T, "resetAggValue", ctx->INT32_T, nullptr); + ctx->resetAggValueFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "resetAggValue", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineGetAggValue(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->getAggValueFunc = defineFunction(module, ctx->DATUM_T, "getAggValue", nullptr); + ctx->getAggValueFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "getAggValue", ctx->INT32_T, nullptr); } inline void defineSetAggValue(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setAggValueFunc = defineFunction(module, ctx->VOID_T, "setAggValue", ctx->DATUM_T, nullptr); + ctx->setAggValueFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "setAggValue", ctx->INT32_T, ctx->DATUM_T, nullptr); } -inline void defineResetAggMaxMinNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) +inline void defineGetAggValueIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->resetAggMaxMinNullFunc = defineFunction(module, ctx->VOID_T, "resetAggMaxMinNull", nullptr); + ctx->getAggValueIsNullFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "getAggValueIsNull", ctx->INT32_T, nullptr); } -inline void defineSetAggMaxMinNotNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) +inline void defineSetAggValueIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->setAggMaxMinNotNullFunc = defineFunction(module, ctx->VOID_T, "setAggMaxMinNotNull", nullptr); -} - -inline void defineGetAggMaxMinIsNull(JitLlvmCodeGenContext* ctx, llvm::Module* module) -{ - ctx->getAggMaxMinIsNullFunc = defineFunction(module, ctx->INT32_T, "getAggMaxMinIsNull", nullptr); + ctx->setAggValueIsNullFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "setAggValueIsNull", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void definePrepareDistinctSet(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->prepareDistinctSetFunc = defineFunction(module, ctx->VOID_T, "prepareDistinctSet", ctx->INT32_T, nullptr); + ctx->prepareDistinctSetFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "prepareDistinctSet", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineInsertDistinctItem(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->insertDistinctItemFunc = - defineFunction(module, ctx->INT32_T, "insertDistinctItem", ctx->INT32_T, ctx->DATUM_T, nullptr); + ctx->insertDistinctItemFunc = llvm_util::DefineFunction( + module, ctx->INT32_T, "insertDistinctItem", ctx->INT32_T, ctx->INT32_T, ctx->DATUM_T, nullptr); } inline void defineDestroyDistinctSet(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->destroyDistinctSetFunc = defineFunction(module, ctx->VOID_T, "destroyDistinctSet", ctx->INT32_T, nullptr); + ctx->destroyDistinctSetFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "destroyDistinctSet", ctx->INT32_T, ctx->INT32_T, nullptr); } inline void defineResetTupleDatum(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->resetTupleDatumFunc = defineFunction(module, + ctx->resetTupleDatumFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "resetTupleDatum", ctx->TupleTableSlotType->getPointerTo(), @@ -571,100 +599,119 @@ inline void defineResetTupleDatum(JitLlvmCodeGenContext* ctx, llvm::Module* modu inline void defineReadTupleDatum(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->readTupleDatumFunc = defineFunction(module, - ctx->DATUM_T, - "readTupleDatum", - ctx->TupleTableSlotType->getPointerTo(), - ctx->INT32_T, - ctx->INT32_T, - nullptr); + ctx->readTupleDatumFunc = llvm_util::DefineFunction( + module, ctx->DATUM_T, "readTupleDatum", ctx->TupleTableSlotType->getPointerTo(), ctx->INT32_T, nullptr); } inline void defineWriteTupleDatum(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->writeTupleDatumFunc = defineFunction(module, + ctx->writeTupleDatumFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "writeTupleDatum", ctx->TupleTableSlotType->getPointerTo(), ctx->INT32_T, ctx->DATUM_T, + ctx->INT32_T, nullptr); } inline void DefineSelectSubQueryResultFunc(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->selectSubQueryResultFunc = defineFunction(module, ctx->DATUM_T, "SelectSubQueryResult", ctx->INT32_T, nullptr); + ctx->selectSubQueryResultFunc = + llvm_util::DefineFunction(module, ctx->DATUM_T, "SelectSubQueryResult", ctx->INT32_T, nullptr); } inline void DefineCopyAggregateToSubQueryResultFunc(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->copyAggregateToSubQueryResultFunc = - defineFunction(module, ctx->VOID_T, "CopyAggregateToSubQueryResult", ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->VOID_T, "CopyAggregateToSubQueryResult", ctx->INT32_T, nullptr); } inline void DefineGetSubQuerySlot(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->GetSubQuerySlotFunc = - defineFunction(module, ctx->TupleTableSlotType->getPointerTo(), "GetSubQuerySlot", ctx->INT32_T, nullptr); + ctx->GetSubQuerySlotFunc = llvm_util::DefineFunction( + module, ctx->TupleTableSlotType->getPointerTo(), "GetSubQuerySlot", ctx->INT32_T, nullptr); } inline void DefineGetSubQueryTable(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->GetSubQueryTableFunc = - defineFunction(module, ctx->TableType->getPointerTo(), "GetSubQueryTable", ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->TableType->getPointerTo(), "GetSubQueryTable", ctx->INT32_T, nullptr); } inline void DefineGetSubQueryIndex(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->GetSubQueryIndexFunc = - defineFunction(module, ctx->IndexType->getPointerTo(), "GetSubQueryIndex", ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->IndexType->getPointerTo(), "GetSubQueryIndex", ctx->INT32_T, nullptr); } inline void DefineGetSubQuerySearchKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { ctx->GetSubQuerySearchKeyFunc = - defineFunction(module, ctx->KeyType->getPointerTo(), "GetSubQuerySearchKey", ctx->INT32_T, nullptr); + llvm_util::DefineFunction(module, ctx->KeyType->getPointerTo(), "GetSubQuerySearchKey", ctx->INT32_T, nullptr); } inline void DefineGetSubQueryEndIteratorKey(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->GetSubQueryEndIteratorKeyFunc = - defineFunction(module, ctx->KeyType->getPointerTo(), "GetSubQueryEndIteratorKey", ctx->INT32_T, nullptr); + ctx->GetSubQueryEndIteratorKeyFunc = llvm_util::DefineFunction( + module, ctx->KeyType->getPointerTo(), "GetSubQueryEndIteratorKey", ctx->INT32_T, nullptr); } inline void DefineGetConstAt(JitLlvmCodeGenContext* ctx, llvm::Module* module) { - ctx->GetConstAtFunc = defineFunction(module, ctx->DATUM_T, "GetConstAt", ctx->INT32_T, ctx->INT32_T, nullptr); + ctx->GetConstAtFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "GetConstAt", ctx->INT32_T, nullptr); +} + +inline void DefineGetInvokeParamListInfo(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->GetInvokeParamListInfoFunc = llvm_util::DefineFunction( + module, ctx->ParamListInfoDataType->getPointerTo(), "GetInvokeParamListInfo", nullptr); +} + +inline void DefineSetParamValue(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->SetParamValueFunc = llvm_util::DefineFunction(module, + ctx->VOID_T, + "SetParamValue", + ctx->ParamListInfoDataType->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->INT64_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineInvokeStoredProcedure(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->InvokeStoredProcedureFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "InvokeStoredProcedure", nullptr); +} + +inline void DefineConvertViaString(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->ConvertViaStringFunc = llvm_util::DefineFunction( + module, ctx->DATUM_T, "JitConvertViaString", ctx->DATUM_T, ctx->INT32_T, ctx->INT32_T, ctx->INT32_T, nullptr); +} + +inline void DefineEmitProfileData(JitLlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->EmitProfileDataFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "EmitProfileData", ctx->INT32_T, ctx->INT32_T, ctx->INT32_T, nullptr); } /*--------------------------- End of LLVM Helper Prototypes ---------------------------*/ /*--------------------------- Helpers to generate calls to Helper function via LLVM ---------------------------*/ -inline llvm::Value* AddFunctionCall(JitLlvmCodeGenContext* ctx, llvm::FunctionCallee func, ...) -{ - va_list vargs; - va_start(vargs, func); - std::vector args; - llvm::Value* arg_value = va_arg(vargs, llvm::Value*); - while (arg_value != nullptr) { - args.push_back(arg_value); - arg_value = va_arg(vargs, llvm::Value*); - } - va_end(vargs); - llvm::ArrayRef argsRef(args); - return ctx->_builder->CreateCall(func, argsRef); -} /** @brief Adds a call to isSoftMemoryLimitReached(). */ inline llvm::Value* AddIsSoftMemoryLimitReached(JitLlvmCodeGenContext* ctx) { - return AddFunctionCall(ctx, ctx->isSoftMemoryLimitReachedFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->isSoftMemoryLimitReachedFunc, nullptr); } /** @brief Adds a call to InitKey(). */ inline void AddInitKey(JitLlvmCodeGenContext* ctx, llvm::Value* key, llvm::Value* index) { - AddFunctionCall(ctx, ctx->initKeyFunc, key, index, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->initKeyFunc, key, index, nullptr); } /** @brief Adds a call to initSearchKey(key, index). */ @@ -679,212 +726,182 @@ inline void AddInitSearchKey(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeS } } -/** @brief Adds a call to getColumnAt(table, colid). */ +/** @brief Adds a call to getColumnAt(table, columnId). */ inline llvm::Value* AddGetColumnAt( - JitLlvmCodeGenContext* ctx, int colid, JitRangeScanType range_scan_type, int subQueryIndex = -1) + JitLlvmCodeGenContext* ctx, int columnId, JitRangeScanType rangeScanType, int subQueryIndex = -1) { - llvm::ConstantInt* colid_value = llvm::ConstantInt::get(ctx->INT32_T, colid, true); - llvm::Value* table = nullptr; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - table = ctx->inner_table_value; - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - table = ctx->table_value; - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - table = ctx->m_subQueryData[subQueryIndex].m_table; - } - return AddFunctionCall(ctx, ctx->getColumnAtFunc, table, colid_value, nullptr); + llvm::Value* table = getExecContextTable(ctx, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall(ctx, ctx->getColumnAtFunc, table, JIT_CONST_INT32(columnId), nullptr); } -/** @brief Adds a call to setExprArgIsNull(arg_pos, isnull). */ -inline void AddSetExprArgIsNull(JitLlvmCodeGenContext* ctx, int arg_pos, int isnull) +inline void AddSetExprIsNull(JitLlvmCodeGenContext* ctx, int isnull) { - llvm::ConstantInt* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - llvm::ConstantInt* isnull_value = llvm::ConstantInt::get(ctx->INT32_T, isnull, true); - AddFunctionCall(ctx, ctx->setExprArgIsNullFunc, arg_pos_value, isnull_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->m_setExprIsNullFunc, JIT_CONST_INT32(isnull), nullptr); } -/** @brief Adds a call to getExprArgIsNull(arg_pos). */ -inline llvm::Value* AddGetExprArgIsNull(JitLlvmCodeGenContext* ctx, int arg_pos) +inline llvm::Value* AddGetExprIsNull(JitLlvmCodeGenContext* ctx) { - llvm::ConstantInt* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall(ctx, ctx->getExprArgIsNullFunc, arg_pos_value, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->m_getExprIsNullFunc, nullptr); } -/** @brief Adds a call to getDatumParam(paramid, arg_pos). */ -inline llvm::Value* AddGetDatumParam(JitLlvmCodeGenContext* ctx, int paramid, int arg_pos) +inline void AddSetExprCollation(JitLlvmCodeGenContext* ctx, int collation) { - llvm::ConstantInt* paramid_value = llvm::ConstantInt::get(ctx->INT32_T, paramid, true); - llvm::ConstantInt* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall(ctx, ctx->getDatumParamFunc, ctx->params_value, paramid_value, arg_pos_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->m_setExprCollationFunc, JIT_CONST_INT32(collation), nullptr); } -/** @brief Adds a call to readDatumColumn(table_colid, arg_pos). */ -inline llvm::Value* AddReadDatumColumn( - JitLlvmCodeGenContext* ctx, llvm::Value* table, llvm::Value* row, int table_colid, int arg_pos) +inline llvm::Value* AddGetExprCollation(JitLlvmCodeGenContext* ctx) { - llvm::ConstantInt* table_colid_value = llvm::ConstantInt::get(ctx->INT32_T, table_colid, true); - llvm::ConstantInt* arg_pos_value = llvm::ConstantInt::get(ctx->INT32_T, arg_pos, true); - return AddFunctionCall( - ctx, ctx->readDatumColumnFunc, ctx->table_value, row, table_colid_value, arg_pos_value, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->m_getExprCollationFunc, nullptr); } -/** @brief Adds a call to writeDatumColumn(table_colid, value). */ -inline void AddWriteDatumColumn(JitLlvmCodeGenContext* ctx, int table_colid, llvm::Value* row, llvm::Value* value) +/** @brief Adds a call to getDatumParam(paramid, argPos). */ +inline llvm::Value* AddGetDatumParam(JitLlvmCodeGenContext* ctx, int paramid) { - // make sure we have a column before issuing the call - // we always write to a main table row (whether UPDATE or range UPDATE, so inner_scan value below is false) - llvm::Value* column = AddGetColumnAt(ctx, table_colid, JIT_RANGE_SCAN_MAIN); - AddFunctionCall(ctx, ctx->writeDatumColumnFunc, row, column, value, nullptr); + return llvm_util::AddFunctionCall( + ctx, ctx->getDatumParamFunc, ctx->params_value, JIT_CONST_INT32(paramid), nullptr); } -/** @brief Adds a call to buildDatumKey(column, key, value, index_colid, offset, value). */ -inline void AddBuildDatumKey(JitLlvmCodeGenContext* ctx, llvm::Value* column, int index_colid, llvm::Value* value, - int value_type, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, int subQueryIndex = -1) +/** @brief Adds a call to readDatumColumn(tableColumnId, argPos). */ +inline llvm::Value* AddReadDatumColumn(JitLlvmCodeGenContext* ctx, llvm::Value* table, llvm::Value* row, + int tableColumnId, int isInnerRow, int subQueryIndex) { - int offset = -1; - int size = -1; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - offset = ctx->_inner_table_info.m_indexColumnOffsets[index_colid]; - size = ctx->_inner_table_info.m_index->GetLengthKeyFields()[index_colid]; - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - offset = ctx->_table_info.m_indexColumnOffsets[index_colid]; - size = ctx->_table_info.m_index->GetLengthKeyFields()[index_colid]; - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - offset = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets[index_colid]; - size = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields()[index_colid]; - } - llvm::ConstantInt* colid_value = llvm::ConstantInt::get(ctx->INT32_T, index_colid, true); - llvm::ConstantInt* offset_value = llvm::ConstantInt::get(ctx->INT32_T, offset, true); - llvm::ConstantInt* value_type_value = llvm::ConstantInt::get(ctx->INT32_T, value_type, true); - llvm::ConstantInt* size_value = llvm::ConstantInt::get(ctx->INT32_T, size, true); - llvm::Value* key_value = getExecContextKey(ctx, range_itr_type, range_scan_type, subQueryIndex); - AddFunctionCall(ctx, - ctx->buildDatumKeyFunc, - column, - key_value, - value, - colid_value, - offset_value, - size_value, - value_type_value, + return AddFunctionCall(ctx, + ctx->readDatumColumnFunc, + table, + row, + JIT_CONST_INT32(tableColumnId), + JIT_CONST_INT32(isInnerRow), + JIT_CONST_INT32(subQueryIndex), nullptr); } -/** @brief Adds a call to setBit(bitmap, colid). */ -inline void AddSetBit(JitLlvmCodeGenContext* ctx, int colid) +/** @brief Adds a call to writeDatumColumn(tableColumnId, value). */ +inline void AddWriteDatumColumn(JitLlvmCodeGenContext* ctx, int tableColumnId, llvm::Value* row, llvm::Value* value) { - llvm::ConstantInt* bit_index = llvm::ConstantInt::get(ctx->INT32_T, colid, true); - AddFunctionCall(ctx, ctx->setBitFunc, ctx->bitmap_value, bit_index, nullptr); + // make sure we have a column before issuing the call + // we always write to a main table row (whether UPDATE or range UPDATE, so inner_scan value below is false) + llvm::Value* column = AddGetColumnAt(ctx, tableColumnId, JIT_RANGE_SCAN_MAIN); + llvm_util::AddFunctionCall(ctx, ctx->writeDatumColumnFunc, row, column, value, nullptr); +} + +/** @brief Adds a call to buildDatumKey(column, key, value, index_colid, offset, value). */ +inline void AddBuildDatumKey(JitLlvmCodeGenContext* ctx, llvm::Value* column, int indexColumnId, llvm::Value* value, + int valueType, JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType, int subQueryIndex = -1) +{ + int offset = -1; + int size = -1; + if (rangeScanType == JIT_RANGE_SCAN_INNER) { + offset = ctx->_inner_table_info.m_indexColumnOffsets[indexColumnId]; + size = ctx->_inner_table_info.m_index->GetLengthKeyFields()[indexColumnId]; + } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { + offset = ctx->_table_info.m_indexColumnOffsets[indexColumnId]; + size = ctx->_table_info.m_index->GetLengthKeyFields()[indexColumnId]; + } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { + offset = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets[indexColumnId]; + size = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields()[indexColumnId]; + } + llvm::Value* key = getExecContextKey(ctx, rangeItrType, rangeScanType, subQueryIndex); + llvm_util::AddFunctionCall(ctx, + ctx->buildDatumKeyFunc, + column, + key, + value, + JIT_CONST_INT32(indexColumnId), + JIT_CONST_INT32(offset), + JIT_CONST_INT32(size), + JIT_CONST_INT32(valueType), + nullptr); +} + +/** @brief Adds a call to setBit(bitmap, columnId). */ +inline void AddSetBit(JitLlvmCodeGenContext* ctx, int columnId) +{ + llvm_util::AddFunctionCall(ctx, ctx->setBitFunc, ctx->bitmap_value, JIT_CONST_INT32(columnId), nullptr); } /** @brief Adds a call to resetBitmapSet(bitmap). */ inline void AddResetBitmapSet(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->resetBitmapSetFunc, ctx->bitmap_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->resetBitmapSetFunc, ctx->bitmap_value, nullptr); } /** @brief Adds a call to writeRow(row, bitmap). */ inline llvm::Value* AddWriteRow(JitLlvmCodeGenContext* ctx, llvm::Value* row) { - return AddFunctionCall(ctx, ctx->writeRowFunc, row, ctx->bitmap_value, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->writeRowFunc, row, ctx->bitmap_value, nullptr); } -/** @brief Adds a call to searchRow(table, key, access_mode). */ +/** @brief Adds a call to searchRow(table, key, accessMode). */ inline llvm::Value* AddSearchRow( - JitLlvmCodeGenContext* ctx, MOT::AccessType access_mode, JitRangeScanType range_scan_type, int subQueryIndex) + JitLlvmCodeGenContext* ctx, MOT::AccessType accessMode, JitRangeScanType rangeScanType, int subQueryIndex = -1) { - llvm::Value* row = nullptr; - llvm::ConstantInt* access_mode_value = llvm::ConstantInt::get(ctx->INT32_T, access_mode, true); - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - row = AddFunctionCall( - ctx, ctx->searchRowFunc, ctx->inner_table_value, ctx->inner_key_value, access_mode_value, nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - row = AddFunctionCall(ctx, ctx->searchRowFunc, ctx->table_value, ctx->key_value, access_mode_value, nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - row = AddFunctionCall( - ctx, ctx->searchRowFunc, subQueryData->m_table, subQueryData->m_searchKey, access_mode_value, nullptr); - } - return row; + llvm::Value* table = getExecContextTable(ctx, rangeScanType, subQueryIndex); + llvm::Value* key = getExecContextKey(ctx, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); + int innerRow = (rangeScanType == JIT_RANGE_SCAN_INNER); + return AddFunctionCall(ctx, + ctx->searchRowFunc, + table, + key, + JIT_CONST_INT32(accessMode), + JIT_CONST_INT32(innerRow), + JIT_CONST_INT32(subQueryIndex), + nullptr); } /** @brief Adds a call to createNewRow(table). */ inline llvm::Value* AddCreateNewRow(JitLlvmCodeGenContext* ctx) { - return AddFunctionCall(ctx, ctx->createNewRowFunc, ctx->table_value, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->createNewRowFunc, ctx->table_value, nullptr); } inline llvm::Value* AddInsertRow(JitLlvmCodeGenContext* ctx, llvm::Value* row) { - return AddFunctionCall(ctx, ctx->insertRowFunc, ctx->table_value, row, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->insertRowFunc, ctx->table_value, row, nullptr); } inline llvm::Value* AddDeleteRow(JitLlvmCodeGenContext* ctx) { - return AddFunctionCall(ctx, ctx->deleteRowFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->deleteRowFunc, nullptr); } /** @brief Adds a call to setRowNullBits(table, row). */ inline void AddSetRowNullBits(JitLlvmCodeGenContext* ctx, llvm::Value* row) { - AddFunctionCall(ctx, ctx->setRowNullBitsFunc, ctx->table_value, row, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->setRowNullBitsFunc, ctx->table_value, row, nullptr); } -/** @brief Adds a call to setExprResultNullBit(table, row, colid). */ -inline llvm::Value* AddSetExprResultNullBit(JitLlvmCodeGenContext* ctx, llvm::Value* row, int colid) +/** @brief Adds a call to setExprResultNullBit(table, row, columnId). */ +inline llvm::Value* AddSetExprResultNullBit(JitLlvmCodeGenContext* ctx, llvm::Value* row, int columnId) { - llvm::ConstantInt* colid_value = llvm::ConstantInt::get(ctx->INT32_T, colid, true); - return AddFunctionCall(ctx, ctx->setExprResultNullBitFunc, ctx->table_value, row, colid_value, nullptr); + return llvm_util::AddFunctionCall( + ctx, ctx->setExprResultNullBitFunc, ctx->table_value, row, JIT_CONST_INT32(columnId), nullptr); } /** @brief Adds a call to execClearTuple(slot). */ inline void AddExecClearTuple(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->execClearTupleFunc, ctx->slot_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->execClearTupleFunc, ctx->slot_value, nullptr); } /** @brief Adds a call to execStoreVirtualTuple(slot). */ inline void AddExecStoreVirtualTuple(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->execStoreVirtualTupleFunc, ctx->slot_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->execStoreVirtualTupleFunc, ctx->slot_value, nullptr); } -/** @brief Adds a call to selectColumn(table, row, slot, colid, column_count). */ +/** @brief Adds a call to selectColumn(table, row, slot, columnId, column_count). */ inline bool AddSelectColumn(JitLlvmCodeGenContext* ctx, llvm::Value* row, int tableColumnId, int tupleColumnId, JitRangeScanType rangeScanType, int subQueryIndex) { - llvm::ConstantInt* table_colid_value = llvm::ConstantInt::get(ctx->INT32_T, tableColumnId, true); - llvm::ConstantInt* tuple_colid_value = llvm::ConstantInt::get(ctx->INT32_T, tupleColumnId, true); - llvm::Value* value = nullptr; - if (rangeScanType == JIT_RANGE_SCAN_INNER) { - value = AddFunctionCall(ctx, - ctx->selectColumnFunc, - ctx->inner_table_value, - row, - ctx->slot_value, - table_colid_value, - tuple_colid_value, - nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { - value = AddFunctionCall(ctx, - ctx->selectColumnFunc, - ctx->table_value, - row, - ctx->slot_value, - table_colid_value, - tuple_colid_value, - nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - value = AddFunctionCall(ctx, - ctx->selectColumnFunc, - subQueryData->m_table, - row, - subQueryData->m_slot, - table_colid_value, - tuple_colid_value, - nullptr); - } + llvm::Value* table = getExecContextTable(ctx, rangeScanType, subQueryIndex); + llvm::Value* value = llvm_util::AddFunctionCall(ctx, + ctx->selectColumnFunc, + table, + row, + ctx->slot_value, + JIT_CONST_INT32(tableColumnId), + JIT_CONST_INT32(tupleColumnId), + nullptr); if (value == nullptr) { return false; @@ -896,186 +913,134 @@ inline bool AddSelectColumn(JitLlvmCodeGenContext* ctx, llvm::Value* row, int ta /** @brief Adds a call to setTpProcessed(tp_processed, rows_processed). */ inline void AddSetTpProcessed(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->setTpProcessedFunc, ctx->tp_processed_value, ctx->rows_processed, nullptr); + llvm::Value* rowsProcessed = ctx->m_builder->CreateLoad(ctx->rows_processed, true); + llvm_util::AddFunctionCall(ctx, ctx->setTpProcessedFunc, ctx->tp_processed_value, rowsProcessed, nullptr); } /** @brief Adds a call to setScanEnded(scan_ended, result). */ inline void AddSetScanEnded(JitLlvmCodeGenContext* ctx, int result) { llvm::ConstantInt* result_value = llvm::ConstantInt::get(ctx->INT32_T, result, true); - AddFunctionCall(ctx, ctx->setScanEndedFunc, ctx->scan_ended_value, result_value, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->setScanEndedFunc, ctx->scan_ended_value, result_value, nullptr); } /** @brief Adds a call to copyKey(index, key, end_iterator_key). */ inline void AddCopyKey(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) { - if (rangeScanType == JIT_RANGE_SCAN_INNER) { - AddFunctionCall(ctx, - ctx->copyKeyFunc, - ctx->inner_index_value, - ctx->inner_key_value, - ctx->inner_end_iterator_key_value, - nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { - AddFunctionCall(ctx, ctx->copyKeyFunc, ctx->index_value, ctx->key_value, ctx->end_iterator_key_value, nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - AddFunctionCall(ctx, - ctx->copyKeyFunc, - subQueryData->m_index, - subQueryData->m_searchKey, - subQueryData->m_endIteratorKey, - nullptr); - } + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + llvm::Value* beginKey = getExecContextKey(ctx, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); + llvm::Value* endKey = getExecContextKey(ctx, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); + AddFunctionCall(ctx, ctx->copyKeyFunc, index, beginKey, endKey, nullptr); } /** @brief Adds a call to FillKeyPattern(key, pattern, offset, size) or FillKeyPattern(end_iterator_key, pattern, * offset, size). */ inline void AddFillKeyPattern(JitLlvmCodeGenContext* ctx, unsigned char pattern, int offset, int size, - JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, int subQueryIndex) + JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType, int subQueryIndex) { - llvm::ConstantInt* pattern_value = llvm::ConstantInt::get(ctx->INT8_T, pattern, true); - llvm::ConstantInt* offset_value = llvm::ConstantInt::get(ctx->INT32_T, offset, true); - llvm::ConstantInt* size_value = llvm::ConstantInt::get(ctx->INT32_T, size, true); - llvm::Value* key_value = getExecContextKey(ctx, range_itr_type, range_scan_type, subQueryIndex); - AddFunctionCall(ctx, ctx->fillKeyPatternFunc, key_value, pattern_value, offset_value, size_value, nullptr); + llvm::Value* key = getExecContextKey(ctx, rangeItrType, rangeScanType, subQueryIndex); + llvm_util::AddFunctionCall(ctx, + ctx->fillKeyPatternFunc, + key, + JIT_CONST_INT8(pattern), + JIT_CONST_INT32(offset), + JIT_CONST_INT32(size), + nullptr); } /** @brief Adds a call to adjustKey(key, index, pattern) or adjustKey(end_iterator_key, index, pattern). */ -inline void AddAdjustKey(JitLlvmCodeGenContext* ctx, unsigned char pattern, JitRangeIteratorType range_itr_type, - JitRangeScanType range_scan_type, int subQueryIndex) +inline void AddAdjustKey(JitLlvmCodeGenContext* ctx, unsigned char pattern, JitRangeIteratorType rangeItrType, + JitRangeScanType rangeScanType, int subQueryIndex) { - llvm::ConstantInt* pattern_value = llvm::ConstantInt::get(ctx->INT8_T, pattern, true); - llvm::Value* key_value = getExecContextKey(ctx, range_itr_type, range_scan_type, subQueryIndex); - llvm::Value* index_value = getExecContextIndex(ctx, range_scan_type, subQueryIndex); - AddFunctionCall(ctx, ctx->adjustKeyFunc, key_value, index_value, pattern_value, nullptr); + llvm::Value* key = getExecContextKey(ctx, rangeItrType, rangeScanType, subQueryIndex); + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + llvm_util::AddFunctionCall(ctx, ctx->adjustKeyFunc, key, index, JIT_CONST_INT8(pattern), nullptr); } /** @brief Adds a call to searchIterator(index, key). */ -inline llvm::Value* AddSearchIterator(JitLlvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitRangeBoundMode range_bound_mode, JitRangeScanType range_scan_type, int subQueryIndex) +inline llvm::Value* AddSearchIterator(JitLlvmCodeGenContext* ctx, JitIndexScanDirection indexScanDirection, + JitRangeBoundMode rangeBoundMode, JitRangeScanType rangeScanType, int subQueryIndex) { - llvm::Value* itr = nullptr; - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - uint64_t include_bound = (range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - llvm::ConstantInt* forward_iterator_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::ConstantInt* include_bound_value = llvm::ConstantInt::get(ctx->INT32_T, include_bound, true); - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - itr = AddFunctionCall(ctx, - ctx->searchIteratorFunc, - ctx->inner_index_value, - ctx->inner_key_value, - forward_iterator_value, - include_bound_value, - nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - itr = AddFunctionCall(ctx, - ctx->searchIteratorFunc, - ctx->index_value, - ctx->key_value, - forward_iterator_value, - include_bound_value, - nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - itr = AddFunctionCall(ctx, - ctx->searchIteratorFunc, - subQueryData->m_index, - subQueryData->m_searchKey, - forward_iterator_value, - include_bound_value, - nullptr); - } - return itr; + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + int includeBound = (rangeBoundMode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + llvm::Value* key = getExecContextKey(ctx, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall( + ctx, ctx->searchIteratorFunc, index, key, JIT_CONST_INT32(forwardScan), JIT_CONST_INT32(includeBound), nullptr); } /** @brief Adds a call to beginIterator(index). */ inline llvm::Value* AddBeginIterator(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) { - llvm::Value* itr = nullptr; - if (rangeScanType == JIT_RANGE_SCAN_INNER) { - itr = AddFunctionCall(ctx, ctx->beginIteratorFunc, ctx->inner_index_value, nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { - itr = AddFunctionCall(ctx, ctx->beginIteratorFunc, ctx->index_value, nullptr); - } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - itr = AddFunctionCall(ctx, ctx->beginIteratorFunc, subQueryData->m_index, nullptr); - } - return itr; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall(ctx, ctx->beginIteratorFunc, index, nullptr); } /** @brief Adds a call to createEndIterator(index, end_iterator_key). */ -inline llvm::Value* AddCreateEndIterator(JitLlvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitRangeBoundMode range_bound_mode, JitRangeScanType range_scan_type, int subQueryIndex = -1) +inline llvm::Value* AddCreateEndIterator(JitLlvmCodeGenContext* ctx, JitIndexScanDirection indexScanDirection, + JitRangeBoundMode rangeBoundMode, JitRangeScanType rangeScanType, int subQueryIndex = -1) { - llvm::Value* itr = nullptr; - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - uint64_t include_bound = (range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - llvm::ConstantInt* forward_scan_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::ConstantInt* include_bound_value = llvm::ConstantInt::get(ctx->INT32_T, include_bound, true); - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - itr = AddFunctionCall(ctx, - ctx->createEndIteratorFunc, - ctx->inner_index_value, - ctx->inner_end_iterator_key_value, - forward_scan_value, - include_bound_value, - nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - itr = AddFunctionCall(ctx, - ctx->createEndIteratorFunc, - ctx->index_value, - ctx->end_iterator_key_value, - forward_scan_value, - include_bound_value, - nullptr); - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitLlvmCodeGenContext::SubQueryData* subQueryData = &ctx->m_subQueryData[subQueryIndex]; - itr = AddFunctionCall(ctx, - ctx->createEndIteratorFunc, - subQueryData->m_index, - subQueryData->m_endIteratorKey, - forward_scan_value, - include_bound_value, - nullptr); - } - return itr; + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + int includeBound = (rangeBoundMode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + llvm::Value* key = getExecContextKey(ctx, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall(ctx, + ctx->createEndIteratorFunc, + index, + key, + JIT_CONST_INT32(forwardScan), + JIT_CONST_INT32(includeBound), + nullptr); } /** @brief Adds a call to isScanEnd(index, iterator, end_iterator). */ -inline llvm::Value* AddIsScanEnd(JitLlvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitLlvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, int subQueryIndex = -1) +inline llvm::Value* AddIsScanEnd(JitLlvmCodeGenContext* ctx, JitIndexScanDirection indexScanDirection, + JitLlvmRuntimeCursor* cursor, JitRangeScanType rangeScanType, int subQueryIndex = -1) { - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - llvm::ConstantInt* forward_scan_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::Value* index_value = getExecContextIndex(ctx, range_scan_type, subQueryIndex); - return AddFunctionCall( - ctx, ctx->isScanEndFunc, index_value, cursor->begin_itr, cursor->end_itr, forward_scan_value, nullptr); + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall( + ctx, ctx->isScanEndFunc, index, cursor->begin_itr, cursor->end_itr, JIT_CONST_INT32(forwardScan), nullptr); +} + +inline llvm::Value* AddCheckRowExistsInIterator(JitLlvmCodeGenContext* ctx, JitIndexScanDirection indexScanDirection, + JitLlvmRuntimeCursor* cursor, JitRangeScanType rangeScanType, int subQueryIndex) +{ + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall(ctx, + ctx->CheckRowExistsInIteratorFunc, + index, + cursor->begin_itr, + cursor->end_itr, + JIT_CONST_INT32(forwardScan), + nullptr); } /** @brief Adds a cal to getRowFromIterator(index, iterator, end_iterator). */ -inline llvm::Value* AddGetRowFromIterator(JitLlvmCodeGenContext* ctx, MOT::AccessType access_mode, - JitIndexScanDirection index_scan_direction, JitLlvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, +inline llvm::Value* AddGetRowFromIterator(JitLlvmCodeGenContext* ctx, MOT::AccessType accessMode, + JitIndexScanDirection indexScanDirection, JitLlvmRuntimeCursor* cursor, JitRangeScanType rangeScanType, int subQueryIndex) { - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - llvm::ConstantInt* access_mode_value = llvm::ConstantInt::get(ctx->INT32_T, access_mode, true); - llvm::ConstantInt* forward_scan_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::Value* index_value = getExecContextIndex(ctx, range_scan_type, subQueryIndex); - return AddFunctionCall(ctx, + int innerRow = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + llvm::Value* index = getExecContextIndex(ctx, rangeScanType, subQueryIndex); + return llvm_util::AddFunctionCall(ctx, ctx->getRowFromIteratorFunc, - index_value, + index, cursor->begin_itr, cursor->end_itr, - access_mode_value, - forward_scan_value, + JIT_CONST_INT32(accessMode), + JIT_CONST_INT32(forwardScan), + JIT_CONST_INT32(innerRow), + JIT_CONST_INT32(subQueryIndex), nullptr); } /** @brief Adds a call to destroyIterator(iterator). */ inline void AddDestroyIterator(JitLlvmCodeGenContext* ctx, llvm::Value* itr) { - AddFunctionCall(ctx, ctx->destroyIteratorFunc, itr, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->destroyIteratorFunc, itr, nullptr); } inline void AddDestroyCursor(JitLlvmCodeGenContext* ctx, JitLlvmRuntimeCursor* cursor) @@ -1088,265 +1053,307 @@ inline void AddDestroyCursor(JitLlvmCodeGenContext* ctx, JitLlvmRuntimeCursor* c /** @brief Adds a call to setStateIterator(itr, begin_itr). */ inline void AddSetStateIterator( - JitLlvmCodeGenContext* ctx, llvm::Value* itr, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) + JitLlvmCodeGenContext* ctx, llvm::Value* itr, JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType) { - uint64_t begin_itr = (range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* begin_itr_value = llvm::ConstantInt::get(ctx->INT32_T, begin_itr, true); - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - AddFunctionCall(ctx, ctx->setStateIteratorFunc, itr, begin_itr_value, inner_scan_value, nullptr); + int beginItr = (rangeItrType == JIT_RANGE_ITERATOR_START) ? 1 : 0; + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + llvm_util::AddFunctionCall( + ctx, ctx->setStateIteratorFunc, itr, JIT_CONST_INT32(beginItr), JIT_CONST_INT32(innerScan), nullptr); } /** @brief Adds a call to isStateIteratorNull(begin_itr). */ inline llvm::Value* AddIsStateIteratorNull( - JitLlvmCodeGenContext* ctx, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) + JitLlvmCodeGenContext* ctx, JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType) { - uint64_t begin_itr = (range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* begin_itr_value = llvm::ConstantInt::get(ctx->INT32_T, begin_itr, true); - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall(ctx, ctx->isStateIteratorNullFunc, begin_itr_value, inner_scan_value, nullptr); + int beginItr = (rangeItrType == JIT_RANGE_ITERATOR_START) ? 1 : 0; + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall( + ctx, ctx->isStateIteratorNullFunc, JIT_CONST_INT32(beginItr), JIT_CONST_INT32(innerScan), nullptr); } /** @brief Adds a call to isStateScanEnd(index, forward_scan). */ inline llvm::Value* AddIsStateScanEnd( - JitLlvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) + JitLlvmCodeGenContext* ctx, JitIndexScanDirection indexScanDirection, JitRangeScanType rangeScanType) { - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* forward_scan_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall(ctx, ctx->isStateScanEndFunc, forward_scan_value, inner_scan_value, nullptr); + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall( + ctx, ctx->isStateScanEndFunc, JIT_CONST_INT32(forwardScan), JIT_CONST_INT32(innerScan), nullptr); } -/** @brief Adds a cal to getRowFromStateIterator(index, access_mode, forward_scan). */ -inline llvm::Value* AddGetRowFromStateIterator(JitLlvmCodeGenContext* ctx, MOT::AccessType access_mode, - JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) +/** @brief Adds a cal to getRowFromStateIterator(index, accessMode, forward_scan). */ +inline llvm::Value* AddGetRowFromStateIterator(JitLlvmCodeGenContext* ctx, MOT::AccessType accessMode, + JitIndexScanDirection indexScanDirection, JitRangeScanType rangeScanType) { - uint64_t forward_scan = (index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* access_mode_value = llvm::ConstantInt::get(ctx->INT32_T, access_mode, true); - llvm::ConstantInt* forward_scan_value = llvm::ConstantInt::get(ctx->INT32_T, forward_scan, true); - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall( - ctx, ctx->getRowFromStateIteratorFunc, access_mode_value, forward_scan_value, inner_scan_value, nullptr); + int forwardScan = (indexScanDirection == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall(ctx, + ctx->getRowFromStateIteratorFunc, + JIT_CONST_INT32(accessMode), + JIT_CONST_INT32(forwardScan), + JIT_CONST_INT32(innerScan), + nullptr); } /** @brief Adds a call to destroyStateIterators(). */ -inline void AddDestroyStateIterators(JitLlvmCodeGenContext* ctx, JitRangeScanType range_scan_type) +inline void AddDestroyStateIterators(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - AddFunctionCall(ctx, ctx->destroyStateIteratorsFunc, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + llvm_util::AddFunctionCall(ctx, ctx->destroyStateIteratorsFunc, JIT_CONST_INT32(innerScan), nullptr); } -/** @brief Adds a call to setStateScanEndFlag(scan_ended). */ -inline void AddSetStateScanEndFlag(JitLlvmCodeGenContext* ctx, int scan_ended, JitRangeScanType range_scan_type) +/** @brief Adds a call to setStateScanEndFlag(scanEnded). */ +inline void AddSetStateScanEndFlag(JitLlvmCodeGenContext* ctx, int scanEnded, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* scan_ended_value = llvm::ConstantInt::get(ctx->INT32_T, scan_ended, true); - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - AddFunctionCall(ctx, ctx->setStateScanEndFlagFunc, scan_ended_value, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + llvm_util::AddFunctionCall( + ctx, ctx->setStateScanEndFlagFunc, JIT_CONST_INT32(scanEnded), JIT_CONST_INT32(innerScan), nullptr); } /** @brief Adds a call to getStateScanEndFlag(). */ -inline llvm::Value* AddGetStateScanEndFlag(JitLlvmCodeGenContext* ctx, JitRangeScanType range_scan_type) +inline llvm::Value* AddGetStateScanEndFlag(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall(ctx, ctx->getStateScanEndFlagFunc, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall(ctx, ctx->getStateScanEndFlagFunc, JIT_CONST_INT32(innerScan), nullptr); } -inline void AddResetStateRow(JitLlvmCodeGenContext* ctx, JitRangeScanType range_scan_type) +inline void AddResetStateRow(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - AddFunctionCall(ctx, ctx->resetStateRowFunc, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + llvm_util::AddFunctionCall(ctx, ctx->resetStateRowFunc, JIT_CONST_INT32(innerScan), nullptr); } -inline void AddSetStateRow(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitRangeScanType range_scan_type) +inline void AddSetStateRow(JitLlvmCodeGenContext* ctx, llvm::Value* row, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - AddFunctionCall(ctx, ctx->setStateRowFunc, row, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + llvm_util::AddFunctionCall(ctx, ctx->setStateRowFunc, row, JIT_CONST_INT32(innerScan), nullptr); } -inline llvm::Value* AddGetStateRow(JitLlvmCodeGenContext* ctx, JitRangeScanType range_scan_type) +inline llvm::Value* AddGetStateRow(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall(ctx, ctx->getStateRowFunc, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall(ctx, ctx->getStateRowFunc, JIT_CONST_INT32(innerScan), nullptr); } inline void AddCopyOuterStateRow(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->copyOuterStateRowFunc, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->copyOuterStateRowFunc, nullptr); } inline llvm::Value* AddGetOuterStateRowCopy(JitLlvmCodeGenContext* ctx) { - return AddFunctionCall(ctx, ctx->getOuterStateRowCopyFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->getOuterStateRowCopyFunc, nullptr); } -inline llvm::Value* AddIsStateRowNull(JitLlvmCodeGenContext* ctx, JitRangeScanType range_scan_type) +inline llvm::Value* AddIsStateRowNull(JitLlvmCodeGenContext* ctx, JitRangeScanType rangeScanType) { - uint64_t inner_scan = (range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - llvm::ConstantInt* inner_scan_value = llvm::ConstantInt::get(ctx->INT32_T, inner_scan, true); - return AddFunctionCall(ctx, ctx->isStateRowNullFunc, inner_scan_value, nullptr); + int innerScan = (rangeScanType == JIT_RANGE_SCAN_INNER) ? 1 : 0; + return llvm_util::AddFunctionCall(ctx, ctx->isStateRowNullFunc, JIT_CONST_INT32(innerScan), nullptr); } inline void AddResetStateLimitCounter(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->resetStateLimitCounterFunc, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->resetStateLimitCounterFunc, nullptr); } inline void AddIncrementStateLimitCounter(JitLlvmCodeGenContext* ctx) { - AddFunctionCall(ctx, ctx->incrementStateLimitCounterFunc, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->incrementStateLimitCounterFunc, nullptr); } inline llvm::Value* AddGetStateLimitCounter(JitLlvmCodeGenContext* ctx) { - return AddFunctionCall(ctx, ctx->getStateLimitCounterFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->getStateLimitCounterFunc, nullptr); } -inline void AddPrepareAvgArray(JitLlvmCodeGenContext* ctx, int element_type, int element_count) +inline void AddPrepareAvgArray(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType, int elementCount) { - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - llvm::ConstantInt* element_count_value = llvm::ConstantInt::get(ctx->INT32_T, element_count, true); - AddFunctionCall(ctx, ctx->prepareAvgArrayFunc, element_type_value, element_count_value, nullptr); + llvm_util::AddFunctionCall(ctx, + ctx->prepareAvgArrayFunc, + JIT_CONST_INT32(aggIndex), + JIT_CONST_INT32(elementType), + JIT_CONST_INT32(elementCount), + nullptr); } -inline llvm::Value* AddLoadAvgArray(JitLlvmCodeGenContext* ctx) +inline llvm::Value* AddLoadAvgArray(JitLlvmCodeGenContext* ctx, int aggIndex) { - return AddFunctionCall(ctx, ctx->loadAvgArrayFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->loadAvgArrayFunc, JIT_CONST_INT32(aggIndex), nullptr); } -inline void AddSaveAvgArray(JitLlvmCodeGenContext* ctx, llvm::Value* avg_array) +inline void AddSaveAvgArray(JitLlvmCodeGenContext* ctx, int aggIndex, llvm::Value* avgArray) { - AddFunctionCall(ctx, ctx->saveAvgArrayFunc, avg_array, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->saveAvgArrayFunc, JIT_CONST_INT32(aggIndex), avgArray, nullptr); } -inline llvm::Value* AddComputeAvgFromArray(JitLlvmCodeGenContext* ctx, int element_type) +inline llvm::Value* AddComputeAvgFromArray(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType) { - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - return AddFunctionCall(ctx, ctx->computeAvgFromArrayFunc, element_type_value, nullptr); + return llvm_util::AddFunctionCall( + ctx, ctx->computeAvgFromArrayFunc, JIT_CONST_INT32(aggIndex), JIT_CONST_INT32(elementType), nullptr); } -inline void AddResetAggValue(JitLlvmCodeGenContext* ctx, int element_type) +inline void AddResetAggValue(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType) { - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - AddFunctionCall(ctx, ctx->resetAggValueFunc, element_type_value, nullptr); + llvm_util::AddFunctionCall( + ctx, ctx->resetAggValueFunc, JIT_CONST_INT32(aggIndex), JIT_CONST_INT32(elementType), nullptr); } -inline void AddResetAggMaxMinNull(JitLlvmCodeGenContext* ctx) +inline llvm::Value* AddGetAggValue(JitLlvmCodeGenContext* ctx, int aggIndex) { - AddFunctionCall(ctx, ctx->resetAggMaxMinNullFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->getAggValueFunc, JIT_CONST_INT32(aggIndex), nullptr); } -inline llvm::Value* AddGetAggValue(JitLlvmCodeGenContext* ctx) +inline void AddSetAggValue(JitLlvmCodeGenContext* ctx, int aggIndex, llvm::Value* value) { - return AddFunctionCall(ctx, ctx->getAggValueFunc, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->setAggValueFunc, JIT_CONST_INT32(aggIndex), value, nullptr); } -inline void AddSetAggValue(JitLlvmCodeGenContext* ctx, llvm::Value* value) +inline llvm::Value* AddGetAggValueIsNull(JitLlvmCodeGenContext* ctx, int aggIndex) { - AddFunctionCall(ctx, ctx->setAggValueFunc, value, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->getAggValueIsNullFunc, JIT_CONST_INT32(aggIndex), nullptr); } -inline void AddSetAggMaxMinNotNull(JitLlvmCodeGenContext* ctx) +inline llvm::Value* AddSetAggValueIsNull(JitLlvmCodeGenContext* ctx, int aggIndex, llvm::Value* isNull) { - AddFunctionCall(ctx, ctx->setAggMaxMinNotNullFunc, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->setAggValueIsNullFunc, JIT_CONST_INT32(aggIndex), isNull, nullptr); } -inline llvm::Value* AddGetAggMaxMinIsNull(JitLlvmCodeGenContext* ctx) +inline void AddPrepareDistinctSet(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType) { - return AddFunctionCall(ctx, ctx->getAggMaxMinIsNullFunc, nullptr); + llvm_util::AddFunctionCall( + ctx, ctx->prepareDistinctSetFunc, JIT_CONST_INT32(aggIndex), JIT_CONST_INT32(elementType), nullptr); } -inline void AddPrepareDistinctSet(JitLlvmCodeGenContext* ctx, int element_type) +inline llvm::Value* AddInsertDistinctItem(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType, llvm::Value* value) { - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - AddFunctionCall(ctx, ctx->prepareDistinctSetFunc, element_type_value, nullptr); + return llvm_util::AddFunctionCall( + ctx, ctx->insertDistinctItemFunc, JIT_CONST_INT32(aggIndex), JIT_CONST_INT32(elementType), value, nullptr); } -inline llvm::Value* AddInsertDistinctItem(JitLlvmCodeGenContext* ctx, int element_type, llvm::Value* value) +inline void AddDestroyDistinctSet(JitLlvmCodeGenContext* ctx, int aggIndex, int elementType) { - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - return AddFunctionCall(ctx, ctx->insertDistinctItemFunc, element_type_value, value, nullptr); -} - -inline void AddDestroyDistinctSet(JitLlvmCodeGenContext* ctx, int element_type) -{ - llvm::ConstantInt* element_type_value = llvm::ConstantInt::get(ctx->INT32_T, element_type, true); - AddFunctionCall(ctx, ctx->destroyDistinctSetFunc, element_type_value, nullptr); + llvm_util::AddFunctionCall( + ctx, ctx->destroyDistinctSetFunc, JIT_CONST_INT32(aggIndex), JIT_CONST_INT32(elementType), nullptr); } /** @brief Adds a call to writeTupleDatum(slot, tuple_colid, value). */ -inline void AddWriteTupleDatum(JitLlvmCodeGenContext* ctx, int tuple_colid, llvm::Value* value) +inline void AddWriteTupleDatum(JitLlvmCodeGenContext* ctx, int tupleColumnId, llvm::Value* value, llvm::Value* isNull) { - llvm::ConstantInt* tuple_colid_value = llvm::ConstantInt::get(ctx->INT32_T, tuple_colid, true); - AddFunctionCall(ctx, ctx->writeTupleDatumFunc, ctx->slot_value, tuple_colid_value, value, nullptr); + llvm_util::AddFunctionCall( + ctx, ctx->writeTupleDatumFunc, ctx->slot_value, JIT_CONST_INT32(tupleColumnId), value, isNull, nullptr); } inline llvm::Value* AddSelectSubQueryResult(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->selectSubQueryResultFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->selectSubQueryResultFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline void AddCopyAggregateToSubQueryResult(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - AddFunctionCall(ctx, ctx->copyAggregateToSubQueryResultFunc, subQueryIndexValue, nullptr); + llvm_util::AddFunctionCall(ctx, ctx->copyAggregateToSubQueryResultFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline llvm::Value* AddGetSubQuerySlot(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->GetSubQuerySlotFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetSubQuerySlotFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline llvm::Value* AddGetSubQueryTable(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->GetSubQueryTableFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetSubQueryTableFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline llvm::Value* AddGetSubQueryIndex(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->GetSubQueryIndexFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetSubQueryIndexFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline llvm::Value* AddGetSubQuerySearchKey(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->GetSubQuerySearchKeyFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetSubQuerySearchKeyFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } inline llvm::Value* AddGetSubQueryEndIteratorKey(JitLlvmCodeGenContext* ctx, int subQueryIndex) { - llvm::ConstantInt* subQueryIndexValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryIndex, true); - return AddFunctionCall(ctx, ctx->GetSubQueryEndIteratorKeyFunc, subQueryIndexValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetSubQueryEndIteratorKeyFunc, JIT_CONST_INT32(subQueryIndex), nullptr); } -inline llvm::Value* AddGetConstAt(JitLlvmCodeGenContext* ctx, int constId, int argPos) +inline llvm::Value* AddGetConstAt(JitLlvmCodeGenContext* ctx, int constId) { - llvm::ConstantInt* constIdValue = llvm::ConstantInt::get(ctx->INT32_T, constId, true); - llvm::ConstantInt* argPosValue = llvm::ConstantInt::get(ctx->INT32_T, argPos, true); - return AddFunctionCall(ctx, ctx->GetConstAtFunc, constIdValue, argPosValue, nullptr); + return llvm_util::AddFunctionCall(ctx, ctx->GetConstAtFunc, JIT_CONST_INT32(constId), nullptr); +} + +inline llvm::Value* AddGetInvokeParamListInfo(JitLlvmCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->GetInvokeParamListInfoFunc, nullptr); +} + +inline void AddSetParamValue(JitLlvmCodeGenContext* ctx, llvm::Value* params, llvm::Value* paramId, int paramType, + llvm::Value* value, llvm::Value* argPos) +{ + llvm_util::AddFunctionCall( + ctx, ctx->SetParamValueFunc, params, paramId, JIT_CONST_INT32(paramType), value, argPos, nullptr); +} + +inline llvm::Value* AddInvokeStoredProcedure(JitLlvmCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->InvokeStoredProcedureFunc, nullptr); +} + +inline llvm::Value* AddConvertViaString(JitLlvmCodeGenContext* ctx, llvm::Value* value, llvm::Value* resultType, + llvm::Value* targetType, llvm::Value* typeMod) +{ + return llvm_util::AddFunctionCall(ctx, ctx->ConvertViaStringFunc, value, resultType, targetType, typeMod, nullptr); +} + +inline void AddEmitProfileData(JitLlvmCodeGenContext* ctx, uint32_t functionId, uint32_t regionId, bool startRegion) +{ + llvm::ConstantInt* functionIdValue = llvm::ConstantInt::get(ctx->INT32_T, functionId, true); + llvm::ConstantInt* regionIdValue = llvm::ConstantInt::get(ctx->INT32_T, regionId, true); + llvm::ConstantInt* startRegionValue = llvm::ConstantInt::get(ctx->INT32_T, startRegion ? 1 : 0, true); + llvm_util::AddFunctionCall( + ctx, ctx->EmitProfileDataFunc, functionIdValue, regionIdValue, startRegionValue, nullptr); +} + +inline void InjectProfileDataImpl(JitLlvmCodeGenContext* ctx, const char* nameSpace, const char* queryString, + const char* regionName, bool beginRegion) +{ + JitProfiler* jitProfiler = JitProfiler::GetInstance(); + uint32_t profileFunctionId = jitProfiler->GetProfileQueryId(nameSpace, queryString); + if (profileFunctionId == MOT_JIT_PROFILE_INVALID_FUNCTION_ID) { + profileFunctionId = jitProfiler->GetProfileFunctionId(queryString, InvalidOid); + } + if (profileFunctionId != MOT_JIT_PROFILE_INVALID_FUNCTION_ID) { + uint32_t profileRegionId = jitProfiler->GetQueryProfileRegionId(nameSpace, queryString, regionName); + if (profileRegionId != MOT_JIT_PROFILE_INVALID_REGION_ID) { + AddEmitProfileData(ctx, profileFunctionId, profileRegionId, beginRegion); + } + } +} + +inline void InjectProfileData( + JitLlvmCodeGenContext* ctx, const char* nameSpace, const char* queryString, bool beginRegion) +{ + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + InjectProfileDataImpl(ctx, nameSpace, queryString, MOT_JIT_PROFILE_REGION_TOTAL, beginRegion); + } +} + +inline void InjectInvokeProfileData( + JitLlvmCodeGenContext* ctx, const char* nameSpace, const char* queryString, bool beginRegion) +{ + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + InjectProfileDataImpl(ctx, nameSpace, queryString, MOT_JIT_PROFILE_REGION_CHILD_CALL, beginRegion); + } } /** @brief Adds a call to issueDebugLog(function, msg). */ #ifdef MOT_JIT_DEBUG inline void IssueDebugLogImpl(JitLlvmCodeGenContext* ctx, const char* function, const char* msg) { - llvm::ConstantInt* function_value = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)function, true); - llvm::ConstantInt* msg_value = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)msg, true); - llvm::Value* function_ptr = llvm::ConstantExpr::getIntToPtr(function_value, ctx->STR_T); - llvm::Value* msg_ptr = llvm::ConstantExpr::getIntToPtr(msg_value, ctx->STR_T); - AddFunctionCall(ctx, ctx->debugLogFunc, function_ptr, msg_ptr, nullptr); + llvm::Value* functionValue = ctx->m_builder->CreateGlobalStringPtr(function); + llvm::Value* msgValue = ctx->m_builder->CreateGlobalStringPtr(msg); + llvm_util::AddFunctionCall(ctx, ctx->debugLogFunc, functionValue, msgValue, nullptr); } #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query.h index 6f996fcc2..a97a22234 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query.h @@ -38,6 +38,7 @@ #include "storage/mot/jit_exec.h" #include "jit_common.h" +#include "jit_llvm_util.h" namespace JitExec { /** @struct Holds instructions that evaluate in runtime to begin and end iterators of a cursor. */ @@ -50,19 +51,7 @@ struct JitLlvmRuntimeCursor { }; /** @struct Code generation context. */ -struct JitLlvmCodeGenContext { - // primitive types - llvm::IntegerType* BOOL_T; - llvm::IntegerType* INT8_T; - llvm::IntegerType* INT16_T; - llvm::IntegerType* INT32_T; - llvm::IntegerType* INT64_T; - llvm::Type* VOID_T; - llvm::Type* FLOAT_T; - llvm::Type* DOUBLE_T; - llvm::PointerType* STR_T; - llvm::IntegerType* DATUM_T; - +struct JitLlvmCodeGenContext : public llvm_util::LlvmCodeGenContext { // PG Types llvm::StructType* ParamExternDataType; llvm::StructType* ParamListInfoDataType; @@ -89,8 +78,10 @@ struct JitLlvmCodeGenContext { llvm::FunctionCallee initKeyFunc; llvm::FunctionCallee getColumnAtFunc; - llvm::FunctionCallee setExprArgIsNullFunc; - llvm::FunctionCallee getExprArgIsNullFunc; + llvm::FunctionCallee m_getExprIsNullFunc; + llvm::FunctionCallee m_setExprIsNullFunc; + llvm::FunctionCallee m_getExprCollationFunc; + llvm::FunctionCallee m_setExprCollationFunc; llvm::FunctionCallee getDatumParamFunc; llvm::FunctionCallee readDatumColumnFunc; llvm::FunctionCallee writeDatumColumnFunc; @@ -119,6 +110,7 @@ struct JitLlvmCodeGenContext { llvm::FunctionCallee beginIteratorFunc; llvm::FunctionCallee createEndIteratorFunc; llvm::FunctionCallee isScanEndFunc; + llvm::FunctionCallee CheckRowExistsInIteratorFunc; llvm::FunctionCallee getRowFromIteratorFunc; llvm::FunctionCallee destroyIteratorFunc; @@ -151,9 +143,8 @@ struct JitLlvmCodeGenContext { llvm::FunctionCallee getAggValueFunc; llvm::FunctionCallee setAggValueFunc; - llvm::FunctionCallee resetAggMaxMinNullFunc; - llvm::FunctionCallee setAggMaxMinNotNullFunc; - llvm::FunctionCallee getAggMaxMinIsNullFunc; + llvm::FunctionCallee getAggValueIsNullFunc; + llvm::FunctionCallee setAggValueIsNullFunc; llvm::FunctionCallee prepareDistinctSetFunc; llvm::FunctionCallee insertDistinctItemFunc; @@ -172,23 +163,12 @@ struct JitLlvmCodeGenContext { llvm::FunctionCallee GetSubQuerySearchKeyFunc; llvm::FunctionCallee GetSubQueryEndIteratorKeyFunc; llvm::FunctionCallee GetConstAtFunc; + llvm::FunctionCallee GetInvokeParamListInfoFunc; + llvm::FunctionCallee SetParamValueFunc; + llvm::FunctionCallee InvokeStoredProcedureFunc; + llvm::FunctionCallee ConvertViaStringFunc; - // builtins -#define APPLY_UNARY_OPERATOR(funcid, name) llvm::FunctionCallee _builtin_##name; -#define APPLY_BINARY_OPERATOR(funcid, name) llvm::FunctionCallee _builtin_##name; -#define APPLY_TERNARY_OPERATOR(funcid, name) llvm::FunctionCallee _builtin_##name; -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) - - APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR + llvm::FunctionCallee EmitProfileDataFunc; // locals llvm::Value* rows_processed; @@ -229,9 +209,10 @@ struct JitLlvmCodeGenContext { uint32_t m_constCount; Const* m_constValues; - dorado::GsCodeGen* _code_gen; - dorado::GsCodeGen::LlvmBuilder* _builder; - llvm::Function* m_jittedQuery; + JitParamInfo* m_paramInfo; + uint32_t m_paramCount; + + const char* m_queryString; }; extern int AllocateConstId(JitLlvmCodeGenContext* ctx, int type, Datum value, bool isNull); diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.cpp b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.cpp index 465debd7f..b8f8ce419 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.cpp @@ -34,6 +34,8 @@ #include "jit_util.h" #include "mot_error.h" #include "utilities.h" +#include "mm_global_api.h" +#include "jit_source_map.h" #ifdef ENABLE_LLVM_COMPILE // for checking if LLVM_ENABLE_DUMP is defined and for using LLVM_VERSION_STRING @@ -51,7 +53,7 @@ static void DestroyCodeGenContext(JitLlvmCodeGenContext* ctx); /** @brief Define all LLVM prototypes. */ void InitCodeGenContextFuncs(JitLlvmCodeGenContext* ctx) { - llvm::Module* module = ctx->_code_gen->module(); + llvm::Module* module = ctx->m_codeGen->module(); // define all function calls defineDebugLog(ctx, module); @@ -60,8 +62,10 @@ void InitCodeGenContextFuncs(JitLlvmCodeGenContext* ctx) defineGetTableIndex(ctx, module); defineInitKey(ctx, module); defineGetColumnAt(ctx, module); - defineSetExprArgIsNull(ctx, module); - defineGetExprArgIsNull(ctx, module); + DefineGetExprIsNull(ctx, module); + DefineSetExprIsNull(ctx, module); + DefineGetExprCollation(ctx, module); + DefineSetExprCollation(ctx, module); defineGetDatumParam(ctx, module); defineReadDatumColumn(ctx, module); defineWriteDatumColumn(ctx, module); @@ -90,6 +94,7 @@ void InitCodeGenContextFuncs(JitLlvmCodeGenContext* ctx) defineBeginIterator(ctx, module); defineCreateEndIterator(ctx, module); defineIsScanEnd(ctx, module); + DefineCheckRowExistsInIterator(ctx, module); defineGetRowFromIterator(ctx, module); defineDestroyIterator(ctx, module); @@ -121,10 +126,8 @@ void InitCodeGenContextFuncs(JitLlvmCodeGenContext* ctx) defineResetAggValue(ctx, module); defineGetAggValue(ctx, module); defineSetAggValue(ctx, module); - - defineResetAggMaxMinNull(ctx, module); - defineSetAggMaxMinNotNull(ctx, module); - defineGetAggMaxMinIsNull(ctx, module); + defineGetAggValueIsNull(ctx, module); + defineSetAggValueIsNull(ctx, module); definePrepareDistinctSet(ctx, module); defineInsertDistinctItem(ctx, module); @@ -143,80 +146,18 @@ void InitCodeGenContextFuncs(JitLlvmCodeGenContext* ctx) DefineGetSubQuerySearchKey(ctx, module); DefineGetSubQueryEndIteratorKey(ctx, module); DefineGetConstAt(ctx, module); -} + DefineGetInvokeParamListInfo(ctx, module); + DefineSetParamValue(ctx, module); + DefineInvokeStoredProcedure(ctx, module); + DefineConvertViaString(ctx, module); -#define APPLY_UNARY_OPERATOR(funcid, name) \ - static void define_builtin_##name(JitLlvmCodeGenContext* ctx, llvm::Module* module) \ - { \ - ctx->_builtin_##name = \ - defineFunction(module, ctx->DATUM_T, "invoke_" #name, ctx->DATUM_T, ctx->INT32_T, nullptr); \ - } - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - static void define_builtin_##name(JitLlvmCodeGenContext* ctx, llvm::Module* module) \ - { \ - ctx->_builtin_##name = \ - defineFunction(module, ctx->DATUM_T, "invoke_" #name, ctx->DATUM_T, ctx->DATUM_T, ctx->INT32_T, nullptr); \ - } - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - static void define_builtin_##name(JitLlvmCodeGenContext* ctx, llvm::Module* module) \ - { \ - ctx->_builtin_##name = defineFunction( \ - module, ctx->DATUM_T, "invoke_" #name, ctx->DATUM_T, ctx->DATUM_T, ctx->DATUM_T, ctx->INT32_T, nullptr); \ - } - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) - -APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -/** @brief Define all operator invocation prototypes. */ -void InitCodeGenContextBuiltins(JitLlvmCodeGenContext* ctx) -{ - llvm::Module* module = ctx->_code_gen->module(); - -#define APPLY_UNARY_OPERATOR(funcid, name) define_builtin_##name(ctx, module); -#define APPLY_BINARY_OPERATOR(funcid, name) define_builtin_##name(ctx, module); -#define APPLY_TERNARY_OPERATOR(funcid, name) define_builtin_##name(ctx, module); -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) APPLY_UNARY_OPERATOR(funcid, name) -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) APPLY_BINARY_OPERATOR(funcid, name) -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) APPLY_TERNARY_OPERATOR(funcid, name) - - APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR + DefineEmitProfileData(ctx, module); } /** @brief Define all LLVM used types synonyms. */ void InitCodeGenContextTypes(JitLlvmCodeGenContext* ctx) { - llvm::LLVMContext& context = ctx->_code_gen->context(); - - // primitive types - ctx->INT8_T = llvm::Type::getInt8Ty(context); - ctx->INT16_T = llvm::Type::getInt16Ty(context); - ctx->INT32_T = llvm::Type::getInt32Ty(context); - ctx->INT64_T = llvm::Type::getInt64Ty(context); - ctx->VOID_T = llvm::Type::getVoidTy(context); - ctx->STR_T = llvm::Type::getInt8Ty(context)->getPointerTo(); - ctx->FLOAT_T = llvm::Type::getFloatTy(context); - ctx->DOUBLE_T = llvm::Type::getDoubleTy(context); - ctx->DATUM_T = ctx->INT64_T; - ctx->BOOL_T = ctx->INT8_T; + llvm::LLVMContext& context = ctx->m_codeGen->context(); // PG types ctx->ParamListInfoDataType = llvm::StructType::create(context, "ParamListInfoData"); @@ -238,24 +179,26 @@ void InitCodeGenContextTypes(JitLlvmCodeGenContext* ctx) /** @brief Initializes a code generation context. */ static bool InitCodeGenContext(JitLlvmCodeGenContext* ctx, GsCodeGen* code_gen, GsCodeGen::LlvmBuilder* builder, - MOT::Table* table, MOT::Index* index, MOT::Table* inner_table = nullptr, MOT::Index* inner_index = nullptr) + MOT::Table* table, MOT::Index* index, MOT::Table* inner_table = nullptr, MOT::Index* inner_index = nullptr, + int invokeParamCount = 0) { // make sure all members are nullptr for proper cleanup in case of failure errno_t erc = memset_s(ctx, sizeof(JitLlvmCodeGenContext), 0, sizeof(JitLlvmCodeGenContext)); securec_check(erc, "\0", "\0"); - ctx->_code_gen = code_gen; - ctx->_builder = builder; - if (!InitTableInfo(&ctx->_table_info, table, index)) { + llvm_util::InitLlvmCodeGenContext(ctx, code_gen, builder); + + if (table && !InitTableInfo(&ctx->_table_info, table, index)) { MOT_REPORT_ERROR( MOT_ERROR_OOM, "JIT Compile", "Failed to initialize table information for code-generation context"); + DestroyCodeGenContext(ctx); return false; } - if (inner_table && !InitTableInfo(&ctx->_inner_table_info, inner_table, inner_index)) { - DestroyTableInfo(&ctx->_table_info); + if (inner_table && inner_index && !InitTableInfo(&ctx->_inner_table_info, inner_table, inner_index)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Compile", "Failed to initialize inner-scan table information for code-generation context"); + DestroyCodeGenContext(ctx); return false; } @@ -271,9 +214,25 @@ static bool InitCodeGenContext(JitLlvmCodeGenContext* ctx, GsCodeGen* code_gen, return false; } + // allocate parameter info on global memory, as it will be passed to the global JIT context + if (invokeParamCount > 0) { + allocSize = sizeof(JitParamInfo) * invokeParamCount; + ctx->m_paramInfo = (JitParamInfo*)MOT::MemGlobalAlloc(allocSize); + if (ctx->m_paramInfo == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for parameter info in code-generation context", + allocSize); + DestroyCodeGenContext(ctx); + return false; + } + erc = memset_s(ctx->m_paramInfo, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + ctx->m_paramCount = invokeParamCount; + } + InitCodeGenContextTypes(ctx); InitCodeGenContextFuncs(ctx); - InitCodeGenContextBuiltins(ctx); return true; } @@ -284,6 +243,8 @@ static bool InitCompoundCodeGenContext(JitLlvmCodeGenContext* ctx, GsCodeGen* co { // execute normal initialization if (!InitCodeGenContext(ctx, code_gen, builder, table, index)) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "JIT Compile", "Failed to initialize table information for code-generation context"); return false; } @@ -312,7 +273,7 @@ static bool InitCompoundCodeGenContext(JitLlvmCodeGenContext* ctx, GsCodeGen* co subIndex = subTable->GetPrimaryIndex(); } else if (subPlan->_plan_type == JIT_PLAN_RANGE_SCAN) { subTable = ((JitRangeSelectPlan*)subPlan)->_index_scan._table; - subIndex = subTable->GetIndex(((JitRangeSelectPlan*)subPlan)->_index_scan._index_id); + subIndex = ((JitRangeSelectPlan*)subPlan)->_index_scan._index; } else { MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "JIT Compile", "Invalid sub-plan %u type: %d", i, (int)subPlan->_plan_type); @@ -367,11 +328,10 @@ static void DestroyCodeGenContext(JitLlvmCodeGenContext* ctx) if (ctx->m_constValues != nullptr) { MOT::MemSessionFree(ctx->m_constValues); } - if (ctx->_code_gen != nullptr) { - ctx->_code_gen->releaseResource(); - delete ctx->_code_gen; - ctx->_code_gen = nullptr; + if (ctx->m_paramInfo != nullptr) { + MOT::MemGlobalFree(ctx->m_paramInfo); } + llvm_util::DestroyLlvmCodeGenContext(ctx); } extern int AllocateConstId(JitLlvmCodeGenContext* ctx, int type, Datum value, bool isNull) @@ -388,30 +348,43 @@ extern int AllocateConstId(JitLlvmCodeGenContext* ctx, int type, Datum value, bo ctx->m_constValues[res].constvalue = value; ctx->m_constValues[res].constisnull = isNull; MOT_LOG_TRACE("Allocated constant id: %d", res); + JIT_PRINT_DATUM(MOT::LogLevel::LL_TRACE, "Allocated constant", type, value, isNull); } return res; } -/** @brief Wraps up an LLVM function (compiles it and prepares a funciton pointer). */ -static JitContext* FinalizeCodegen(JitLlvmCodeGenContext* ctx, int max_arg, JitCommandType command_type) +/** @brief Wraps up an LLVM function (compiles it and prepares a function pointer). */ +static MotJitContext* FinalizeCodegen( + JitLlvmCodeGenContext* ctx, JitCommandType command_type, const char* queryString, JitCodegenStats& codegenStats) { // do GsCodeGen stuff to wrap up - if (!ctx->_code_gen->verifyFunction(ctx->m_jittedQuery)) { + uint64_t startTime = GetSysClock(); + if (!ctx->m_codeGen->verifyFunction(ctx->m_jittedFunction)) { MOT_LOG_ERROR("Failed to generate jitted code for query: Failed to verify jit function"); #ifdef LLVM_ENABLE_DUMP - ctx->m_jittedQuery->dump(); + ctx->m_jittedFunction->dump(); #else - ctx->m_jittedQuery->print(llvm::errs(), nullptr, false, true); + ctx->m_jittedFunction->print(llvm::errs(), nullptr, false, true); #endif return nullptr; } - ctx->_code_gen->FinalizeFunction(ctx->m_jittedQuery); + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_verifyTime = timeMicros; + MOT_LOG_TRACE("Query '%s' verification time: %" PRIu64 " micros", queryString, timeMicros); + + startTime = GetSysClock(); + ctx->m_codeGen->FinalizeFunction(ctx->m_jittedFunction); + endTime = GetSysClock(); + timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_finalizeTime = timeMicros; + MOT_LOG_TRACE("Query '%s' finalization time: %" PRIu64 " micros", queryString, timeMicros); if (IsMotCodegenPrintEnabled()) { #ifdef LLVM_ENABLE_DUMP - ctx->m_jittedQuery->dump(); + ctx->m_jittedFunction->dump(); #else - ctx->m_jittedQuery->print(llvm::errs()); + ctx->m_jittedFunction->print(llvm::errs()); #endif } @@ -425,58 +398,75 @@ static JitContext* FinalizeCodegen(JitLlvmCodeGenContext* ctx, int max_arg, JitC } // that's it, we are ready - JitContext* jit_context = AllocJitContext(JIT_CONTEXT_GLOBAL); + JitQueryContext* jit_context = + (JitQueryContext*)AllocJitContext(JIT_CONTEXT_GLOBAL, JitContextType::JIT_CONTEXT_TYPE_QUERY); if (jit_context == nullptr) { MOT_LOG_TRACE("Failed to allocate JIT context, aborting code generation"); return nullptr; } MOT_LOG_DEBUG("Adding function to MCJit"); - ctx->_code_gen->addFunctionToMCJit(ctx->m_jittedQuery, (void**)&jit_context->m_llvmFunction); + ctx->m_codeGen->addFunctionToMCJit(ctx->m_jittedFunction, (void**)&jit_context->m_llvmFunction); MOT_LOG_DEBUG("Generating code..."); - ctx->_code_gen->enableOptimizations(true); - ctx->_code_gen->compileCurrentModule(false); - MOT_LOG_DEBUG( - "MOT jitted query: code generated at %p --> %p", &jit_context->m_llvmFunction, jit_context->m_llvmFunction); + startTime = GetSysClock(); + ctx->m_codeGen->enableOptimizations(true); + ctx->m_codeGen->compileCurrentModule(false); + endTime = GetSysClock(); + timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_compileTime = timeMicros; + MOT_LOG_TRACE("Query '%s' compilation time: %" PRIu64 " micros", queryString, timeMicros); // setup execution details jit_context->m_table = ctx->_table_info.m_table; + if (jit_context->m_table != nullptr) { + jit_context->m_tableId = jit_context->m_table->GetTableExId(); + MOT_LOG_TRACE("Installed table id: %" PRIu64, jit_context->m_tableId); + } jit_context->m_index = ctx->_table_info.m_index; - jit_context->m_indexId = jit_context->m_index->GetExtId(); - MOT_LOG_TRACE("Installed index id: %" PRIu64, jit_context->m_indexId); - jit_context->m_codeGen = ctx->_code_gen; // steal the context - ctx->_code_gen = nullptr; // prevent destruction - jit_context->m_argCount = max_arg + 1; + if (jit_context->m_index != nullptr) { + jit_context->m_indexId = jit_context->m_index->GetExtId(); + MOT_LOG_TRACE("Installed index id: %" PRIu64, jit_context->m_indexId); + } + jit_context->m_codeGen = ctx->m_codeGen; // steal the context + ctx->m_codeGen = nullptr; // prevent destruction jit_context->m_innerTable = ctx->_inner_table_info.m_table; + if (jit_context->m_innerTable != nullptr) { + jit_context->m_innerTableId = jit_context->m_innerTable->GetTableExId(); + MOT_LOG_TRACE("Installed inner table id: %" PRIu64, jit_context->m_innerTableId); + } jit_context->m_innerIndex = ctx->_inner_table_info.m_index; if (jit_context->m_innerIndex != nullptr) { jit_context->m_innerIndexId = jit_context->m_innerIndex->GetExtId(); MOT_LOG_TRACE("Installed inner index id: %" PRIu64, jit_context->m_innerIndexId); } + jit_context->m_aggCount = 0; jit_context->m_commandType = command_type; + jit_context->m_subQueryCount = 0; + jit_context->m_invokeParamCount = 0; + jit_context->m_invokeParamInfo = nullptr; jit_context->m_constDatums.m_datumCount = datumArray.m_datumCount; jit_context->m_constDatums.m_datums = datumArray.m_datums; + jit_context->m_validState = JIT_CONTEXT_VALID; return jit_context; } -static JitContext* JitUpdateCodegen(Query* query, const char* query_string, JitUpdatePlan* plan) +static inline void PrintErrorInfo(const char* queryString) { - MOT_LOG_DEBUG("Generating code for MOT update at thread %p", (void*)pthread_self()); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while generating JIT LLVM code for query '%s': %s", queryString, edata->message); + FlushErrorState(); + FreeErrorData(edata); +} - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); +#define JIT_TRACE_FAIL(queryType, reason) \ + MOT_LOG_TRACE("Failed to generate jitted code for %s query: %s", queryType, reason) - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; +static MotJitContext* JitUpdateCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitUpdatePlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT update at thread %p for query: %s", (void*)pthread_self(), query_string); // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedUpdate"); @@ -488,11 +478,19 @@ static JitContext* JitUpdateCodegen(Query* query, const char* query_string, JitU // initialize rows_processed local variable buildResetRowsProcessed(ctx); + // build one time filters if required + if (plan->_query.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_query.m_oneTimeFilters)) { + JIT_TRACE_FAIL("UPDATE", "Failed to build one-time filters"); + return nullptr; + } + } else { + MOT_LOG_TRACE("One-time filters not specified"); + } + // begin the WHERE clause (this is a point query - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + if (!buildPointScan(ctx, &plan->_query._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("UPDATE", "Unsupported WHERE clause type"); return nullptr; } @@ -501,9 +499,8 @@ static JitContext* JitUpdateCodegen(Query* query, const char* query_string, JitU llvm::Value* row = buildSearchRow(ctx, MOT::AccessType::WR, JIT_RANGE_SCAN_MAIN); // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: unsupported filter"); - DestroyCodeGenContext(ctx); + if (!buildFilterRow(ctx, row, nullptr, &plan->_query._filters, nullptr)) { + JIT_TRACE_FAIL("UPDATE", "Unsupported filter"); return nullptr; } @@ -513,9 +510,8 @@ static JitContext* JitUpdateCodegen(Query* query, const char* query_string, JitU // now begin updating columns IssueDebugLog("Updating row columns"); - if (!writeRowColumns(ctx, row, &plan->_update_exprs, &max_arg, true)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: failed to process target entry"); - DestroyCodeGenContext(ctx); + if (!writeRowColumns(ctx, row, &plan->_update_exprs, true)) { + JIT_TRACE_FAIL("UPDATE", "Failed to process target entry"); return nullptr; } @@ -532,35 +528,20 @@ static JitContext* JitUpdateCodegen(Query* query, const char* query_string, JitU // signal to envelope executor scan ended AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_UPDATE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + return FinalizeCodegen(ctx, JIT_COMMAND_UPDATE, query_string, codegenStats); } -static JitContext* JitRangeUpdateCodegen(Query* query, const char* query_string, JitRangeUpdatePlan* plan) +static MotJitContext* JitRangeUpdateCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitRangeUpdatePlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT range update at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_index_scan._table; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedRangeUpdate"); IssueDebugLog("Starting execution of jitted range UPDATE"); @@ -572,13 +553,10 @@ static JitContext* JitRangeUpdateCodegen(Query* query, const char* query_string, buildResetRowsProcessed(ctx); // begin the WHERE clause - int max_arg = 0; MOT_LOG_DEBUG("Generating range cursor for range UPDATE query"); - JitLlvmRuntimeCursor cursor = - buildRangeCursor(ctx, &plan->_index_scan, &max_arg, JIT_RANGE_SCAN_MAIN, JIT_INDEX_SCAN_FORWARD, nullptr); + JitLlvmRuntimeCursor cursor = buildRangeCursor(ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, nullptr); if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for range UPDATE query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Range UPDATE", "Unsupported WHERE clause type"); return nullptr; } @@ -586,33 +564,38 @@ static JitContext* JitRangeUpdateCodegen(Query* query, const char* query_string, JIT_WHILE_BEGIN(cursor_loop) llvm::Value* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - llvm::Value* row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), MOT::AccessType::WR, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); + JIT_WHILE_EVAL_NOT(res); + { + llvm::Value* row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + MOT::AccessType::WR, + plan->_index_scan._scan_direction, + &cursor, + JIT_RANGE_SCAN_MAIN); - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_index_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for range UPDATE query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; + // check for additional filters + if (!buildFilterRow(ctx, row, nullptr, &plan->_index_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Range UPDATE", "Unsupported filter"); + return nullptr; + } + + // now begin updating columns + IssueDebugLog("Updating row columns"); + if (!writeRowColumns(ctx, row, &plan->_update_exprs, true)) { + JIT_TRACE_FAIL("Range UPDATE", "Failed to process target entry"); + return nullptr; + } + + IssueDebugLog("Writing row"); + buildWriteRow(ctx, row, false, &cursor); + + // the next call will be executed only if the previous call to writeRow succeeded + buildIncrementRowsProcessed(ctx); + + // reset bitmap for next loop + AddResetBitmapSet(ctx); } - - // now begin updating columns - IssueDebugLog("Updating row columns"); - if (!writeRowColumns(ctx, row, &plan->_update_exprs, &max_arg, true)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: failed to process target entry"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - IssueDebugLog("Writing row"); - buildWriteRow(ctx, row, false, &cursor); - - // the next call will be executed only if the previous call to writeRow succeeded - buildIncrementRowsProcessed(ctx); - - // reset bitmap for next loop - AddResetBitmapSet(ctx); JIT_WHILE_END() // cleanup @@ -625,35 +608,20 @@ static JitContext* JitRangeUpdateCodegen(Query* query, const char* query_string, // signal to envelope executor scan ended AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_UPDATE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + return FinalizeCodegen(ctx, JIT_COMMAND_RANGE_UPDATE, query_string, codegenStats); } -static JitContext* JitInsertCodegen(Query* query, const char* query_string, JitInsertPlan* plan) +static MotJitContext* JitInsertCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitInsertPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT insert at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_table; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedInsert"); IssueDebugLog("Starting execution of jitted INSERT"); @@ -672,10 +640,8 @@ static JitContext* JitInsertCodegen(Query* query, const char* query_string, JitI buildResetRowsProcessed(ctx); IssueDebugLog("Setting row columns"); - int max_arg = 0; - if (!writeRowColumns(ctx, row, &plan->_insert_exprs, &max_arg, false)) { - MOT_LOG_TRACE("Failed to generate jitted code for insert query: failed to process target entry"); - DestroyCodeGenContext(ctx); + if (!writeRowColumns(ctx, row, &plan->_insert_exprs, false)) { + JIT_TRACE_FAIL("INSERT", "Failed to process target entry"); return nullptr; } @@ -691,35 +657,20 @@ static JitContext* JitInsertCodegen(Query* query, const char* query_string, JitI // signal to envelope executor scan ended AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_INSERT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + return FinalizeCodegen(ctx, JIT_COMMAND_INSERT, query_string, codegenStats); } -static JitContext* JitDeleteCodegen(Query* query, const char* query_string, JitDeletePlan* plan) +static MotJitContext* JitDeleteCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitDeletePlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT delete at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedDelete"); IssueDebugLog("Starting execution of jitted DELETE"); @@ -728,10 +679,8 @@ static JitContext* JitDeleteCodegen(Query* query, const char* query_string, JitD buildResetRowsProcessed(ctx); // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for DELETE query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + if (!buildPointScan(ctx, &plan->_query._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("DELETE", "Unsupported WHERE clause type"); return nullptr; } @@ -739,14 +688,13 @@ static JitContext* JitDeleteCodegen(Query* query, const char* query_string, JitD llvm::Value* row = buildSearchRow(ctx, MOT::AccessType::DEL, JIT_RANGE_SCAN_MAIN); // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for DELETE query: unsupported filter"); - DestroyCodeGenContext(ctx); + if (!buildFilterRow(ctx, row, nullptr, &plan->_query._filters, nullptr)) { + JIT_TRACE_FAIL("DELETE", "Unsupported filter"); return nullptr; } - IssueDebugLog("Deleting row"); // row is already cached in concurrency control module, so we do not need to provide an argument + IssueDebugLog("Deleting row"); buildDeleteRow(ctx); // the next call will be executed only if the previous call to deleteRow succeeded @@ -758,35 +706,89 @@ static JitContext* JitDeleteCodegen(Query* query, const char* query_string, JitD // signal to envelope executor scan ended AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_DELETE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + return FinalizeCodegen(ctx, JIT_COMMAND_DELETE, query_string, codegenStats); } -static JitContext* JitSelectCodegen(const Query* query, const char* query_string, JitSelectPlan* plan) +static MotJitContext* JitRangeDeleteCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitRangeDeletePlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT range delete at thread %p", (void*)pthread_self()); + + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedRangeDelete"); + IssueDebugLog("Starting execution of jitted range DELETE"); + + // initialize rows_processed local variable + buildResetRowsProcessed(ctx); + + // begin the WHERE clause + MOT_LOG_DEBUG("Generating range cursor for range DELETE query"); + JitLlvmRuntimeCursor cursor = buildRangeCursor(ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, nullptr); + if (cursor.begin_itr == nullptr) { + JIT_TRACE_FAIL("Range DELETE", "Unsupported WHERE clause type"); + return nullptr; + } + + JIT_WHILE_BEGIN(cursor_loop) + llvm::Value* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); + JIT_WHILE_EVAL_NOT(res); + { + llvm::Value* row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + MOT::AccessType::DEL, + plan->_index_scan._scan_direction, + &cursor, + JIT_RANGE_SCAN_MAIN); + + // check for additional filters + if (!buildFilterRow(ctx, row, nullptr, &plan->_index_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Range DELETE", "Unsupported filter"); + return nullptr; + } + + IssueDebugLog("Deleting row"); + // row is already cached in concurrency control module, so we do not need to provide an argument + buildDeleteRow(ctx); + + // the next call will be executed only if the previous call to deleteRow succeeded + buildIncrementRowsProcessed(ctx); + + // impose limit clause if any + BuildCheckLimitNoState(ctx, plan->_limit_count); + } + JIT_WHILE_END() + + // cleanup + IssueDebugLog("Reached end of range delete loop"); + AddDestroyCursor(ctx, &cursor); + + // execute *tp_processed = rows_processed + AddSetTpProcessed(ctx); + + // signal to envelope executor scan ended + AddSetScanEnded(ctx, 1); + + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + + // return success from calling function + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); + + // wrap up + return FinalizeCodegen(ctx, JIT_COMMAND_RANGE_DELETE, query_string, codegenStats); +} + +static MotJitContext* JitSelectCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitSelectPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT select at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedSelect"); IssueDebugLog("Starting execution of jitted SELECT"); @@ -794,33 +796,39 @@ static JitContext* JitSelectCodegen(const Query* query, const char* query_string // initialize rows_processed local variable buildResetRowsProcessed(ctx); - // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - // clear tuple even if row is not found later AddExecClearTuple(ctx); + // build one time filters if required + if (plan->_query.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_query.m_oneTimeFilters)) { + JIT_TRACE_FAIL("SELECT", "Failed to build one-time filters"); + return nullptr; + } + } else { + MOT_LOG_TRACE("One-time filters not specified"); + } + + // begin the WHERE clause + if (!buildPointScan(ctx, &plan->_query._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("SELECT", "Unsupported WHERE clause type"); + return nullptr; + } + // fetch row for read MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; llvm::Value* row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT query: unsupported filter"); - DestroyCodeGenContext(ctx); + if (!buildFilterRow(ctx, row, nullptr, &plan->_query._filters, nullptr)) { + JIT_TRACE_FAIL("SELECT", "Unsupported filter"); return nullptr; } // now begin selecting columns into result IssueDebugLog("Selecting columns into result"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for insert query: failed to process target entry"); - DestroyCodeGenContext(ctx); + if (!selectRowColumns(ctx, row, &plan->_select_exprs)) { + JIT_TRACE_FAIL("SELECT", "Failed to process target entry"); return nullptr; } @@ -835,48 +843,38 @@ static JitContext* JitSelectCodegen(const Query* query, const char* query_string // signal to envelope executor scan ended (this is a point query) AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_SELECT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_SELECT, query_string, codegenStats); + return jitContext; } static void AddCleanupOldScan(JitLlvmCodeGenContext* ctx) { + // emit code to cleanup previous scan in case this is a new scan JIT_IF_BEGIN(cleanup_old_scan) - JIT_IF_EVAL(ctx->isNewScanValue) - IssueDebugLog("Destroying state iterators due to new scan"); - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); - // sub-query does not have a stateful execution, so no need to cleanup + JIT_IF_EVAL(ctx->isNewScanValue); + { + IssueDebugLog("Destroying state iterators due to new scan"); + AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); + AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); + AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); + AddResetStateRow(ctx, JIT_RANGE_SCAN_INNER); + // sub-query does not have a stateful execution, so no need to cleanup + } JIT_IF_END() } /** @brief Generates code for range SELECT query with a possible LIMIT clause. */ -static JitContext* JitRangeSelectCodegen(const Query* query, const char* query_string, JitRangeSelectPlan* plan) +static MotJitContext* JitRangeSelectCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitRangeSelectPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT select at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_index_scan._table; - int index_id = plan->_index_scan._index_id; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetIndex(index_id))) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedRangeSelect"); IssueDebugLog("Starting execution of jitted range SELECT"); @@ -890,24 +888,42 @@ static JitContext* JitRangeSelectCodegen(const Query* query, const char* query_s // emit code to cleanup previous scan in case this is a new scan AddCleanupOldScan(ctx); + // build one time filters if required, but only if scan is new + bool result = true; + JIT_IF_BEGIN(cleanup_old_scan) + JIT_IF_EVAL(ctx->isNewScanValue); + { + IssueDebugLog("Checking for one-time filters due to new scan"); + if (plan->_index_scan.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_index_scan.m_oneTimeFilters)) { + JIT_TRACE_FAIL("Range SELECT", "Failed to build one-time filters"); + result = false; + } + } else { + MOT_LOG_TRACE("One-time filters not specified"); + } + } + JIT_IF_END() + if (!result) { + return nullptr; + } + // prepare stateful scan if not done so already, if no row exists then emit code to return from function - int max_arg = 0; MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - llvm::Value* row = buildPrepareStateScanRow( - ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, nullptr); + llvm::Value* row = + buildPrepareStateScanRow(ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, access_mode, nullptr, nullptr, nullptr); if (row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for range select query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Range SELECT", "Unsupported WHERE clause type"); return nullptr; } // select inner and outer row expressions into result tuple (no aggregate because aggregate is not stateful) IssueDebugLog("Retrieved row from state iterator, beginning to select columns into result tuple"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for range SELECT query: failed to select row expressions"); - DestroyCodeGenContext(ctx); + if (!selectRowColumns(ctx, row, &plan->_select_exprs)) { + JIT_TRACE_FAIL("Range SELECT", "Failed to select row expressions"); return nullptr; } + AddExecStoreVirtualTuple(ctx); buildIncrementRowsProcessed(ctx); @@ -920,40 +936,40 @@ static JitContext* JitRangeSelectCodegen(const Query* query, const char* query_s // execute *tp_processed = rows_processed AddSetTpProcessed(ctx); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up JitCommandType cmdType = (plan->_index_scan._scan_type == JIT_INDEX_SCAN_FULL) ? JIT_COMMAND_FULL_SELECT : JIT_COMMAND_RANGE_SELECT; - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, cmdType); - // cleanup - DestroyCodeGenContext(ctx); + MotJitContext* jitContext = FinalizeCodegen(ctx, cmdType, query_string, codegenStats); - return jit_context; + if (jitContext != nullptr) { + if (cmdType == JIT_COMMAND_RANGE_SELECT && plan->m_nonNativeSortParams) { + // This query is using non-native sort. clone the relevant data to context + JitQueryContext* jitQueryContext = (JitQueryContext*)jitContext; + jitQueryContext->m_nonNativeSortParams = + CloneJitNonNativeSortParams(plan->m_nonNativeSortParams, JIT_CONTEXT_GLOBAL); + if (jitQueryContext->m_nonNativeSortParams == nullptr) { + MOT_LOG_ERROR("Failed to clone non native sort data into context."); + DestroyJitContext(jitContext); + jitContext = nullptr; + } + } + } + + return jitContext; } /** @brief Generates code for range SELECT query with aggregator. */ -static JitContext* JitAggregateRangeSelectCodegen( - const Query* query, const char* query_string, JitRangeSelectPlan* plan) +static MotJitContext* JitAggregateRangeSelectCodegen(JitLlvmCodeGenContext* ctx, const Query* query, + const char* query_string, JitRangeSelectPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT aggregate range select at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - MOT::Table* table = plan->_index_scan._table; - int index_id = plan->_index_scan._index_id; - JitLlvmCodeGenContext cg_ctx = {0}; - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetIndex(index_id))) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedAggregateRangeSelect"); IssueDebugLog("Starting execution of jitted aggregate range SELECT"); @@ -965,61 +981,78 @@ static JitContext* JitAggregateRangeSelectCodegen( AddExecClearTuple(ctx); // prepare for aggregation - if (!prepareAggregate(ctx, &plan->_aggregate)) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: failed to prepare aggregate"); - DestroyCodeGenContext(ctx); + if (!prepareAggregates(ctx, plan->m_aggregates, plan->m_aggCount)) { + JIT_TRACE_FAIL("Aggregate Range SELECT", "Failed to prepare aggregates"); return nullptr; } - AddResetStateLimitCounter(ctx); + // counter for number of aggregates operates on a single row + llvm::Value* aggCount = ctx->m_builder->CreateAlloca(ctx->INT32_T, 0, nullptr, "agg_count"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount, true); // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - // begin the WHERE clause - int max_arg = 0; - JitIndexScanDirection indexScanDirection = JIT_INDEX_SCAN_FORWARD; + // build one time filters if required + llvm::LLVMContext& context = ctx->m_codeGen->context(); + DEFINE_BLOCK(build_agg_select_result, ctx->m_jittedFunction); + if (plan->_index_scan.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_index_scan.m_oneTimeFilters, build_agg_select_result)) { + JIT_TRACE_FAIL("Aggregate Range SELECT", "Failed to build one-time filters"); + return nullptr; + } + } else { + MOT_LOG_TRACE("One-time filters not specified"); + } + // begin the WHERE clause // build range iterators MOT_LOG_DEBUG("Generating range cursor for range SELECT query"); - JitLlvmRuntimeCursor cursor = - buildRangeCursor(ctx, &plan->_index_scan, &max_arg, JIT_RANGE_SCAN_MAIN, indexScanDirection, nullptr); + JitLlvmRuntimeCursor cursor = buildRangeCursor(ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, nullptr); if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Aggregate Range SELECT", "Unsupported WHERE clause type"); return nullptr; } + // when we have select count(*) without any filters we can optimize and skip row copy + bool checkRowExistOnly = false; + if ((plan->m_aggCount == 1) && (plan->m_aggregates[0]._aggreaget_op == JIT_AGGREGATE_COUNT) && + (plan->m_aggregates[0]._table == nullptr) && (plan->_index_scan._filters._filter_count == 0)) { + checkRowExistOnly = true; + } + + llvm::Value* row = nullptr; JIT_WHILE_BEGIN(cursor_aggregate_loop) - llvm::Value* res = AddIsScanEnd(ctx, indexScanDirection, &cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - llvm::Value* row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), access_mode, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); + llvm::Value* res = AddIsScanEnd(ctx, plan->_index_scan._scan_direction, &cursor, JIT_RANGE_SCAN_MAIN); + JIT_WHILE_EVAL_NOT(res); + { + if (checkRowExistOnly) { + BuildCheckRowExistsInIterator( + ctx, JIT_WHILE_POST_BLOCK(), plan->_index_scan._scan_direction, &cursor, JIT_RANGE_SCAN_MAIN); + } else { + row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + access_mode, + plan->_index_scan._scan_direction, + &cursor, + JIT_RANGE_SCAN_MAIN); + // check for additional filters, if not try to fetch next row + if (!buildFilterRow(ctx, row, nullptr, &plan->_index_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Aggregate Range SELECT", "Unsupported filter"); + return nullptr; + } + } - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, row, &plan->_index_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // if row disqualified due to DISTINCT operator then go back to loop test block - if (!buildAggregateRow(ctx, &plan->_aggregate, row, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // if a limit clause exists, then increment limit counter and check if reached limit - if (plan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - llvm::Value* current_limit_count = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(current_limit_count, JIT_CONST(plan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - JIT_WHILE_BREAK() // break from loop - JIT_IF_END() + // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) + // if row disqualified due to DISTINCT operator then go back to loop test block + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount); + for (int i = 0; i < plan->m_aggCount; ++i) { + if (!buildAggregateRow(ctx, &plan->m_aggregates[i], i, row, nullptr, aggCount)) { + JIT_TRACE_FAIL("Aggregate Range SELECT", "Unsupported aggregate"); + return nullptr; + } + } } JIT_WHILE_END() @@ -1028,49 +1061,63 @@ static JitContext* JitAggregateRangeSelectCodegen( AddDestroyCursor(ctx, &cursor); // wrap up aggregation and write to result tuple - buildAggregateResult(ctx, &plan->_aggregate); + JIT_GOTO(build_agg_select_result); + ctx->m_builder->SetInsertPoint(build_agg_select_result); + buildAggregateResult(ctx, plan->m_aggregates, plan->m_aggCount); // store the result tuple AddExecStoreVirtualTuple(ctx); // execute *tp_processed = rows_processed + buildIncrementRowsProcessed(ctx); // aggregate ALWAYS has at least one row processed AddSetTpProcessed(ctx); // signal to envelope executor scan ended (this is an aggregate loop) AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_AGGREGATE_RANGE_SELECT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + JitQueryContext* jitContext = + (JitQueryContext*)FinalizeCodegen(ctx, JIT_COMMAND_AGGREGATE_RANGE_SELECT, query_string, codegenStats); + if (jitContext != nullptr) { + jitContext->m_aggCount = plan->m_aggCount; + } + return jitContext; } -static JitContext* JitPointJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) +static bool BuildOneTimeJoinFilters( + JitLlvmCodeGenContext* ctx, JitJoinPlan* plan, const char* planType, llvm::BasicBlock* nextBlock = nullptr) +{ + if (plan->_outer_scan.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_outer_scan.m_oneTimeFilters, nextBlock)) { + JIT_TRACE_FAIL(planType, "Failed to build outer-scan one-time filters"); + return false; + } + } else { + MOT_LOG_TRACE("One-time filters not specified for outer-scan"); + } + + // build one time filters if required + if (plan->_inner_scan.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &plan->_inner_scan.m_oneTimeFilters, nextBlock)) { + JIT_TRACE_FAIL(planType, "Failed to build inner-scan one-time filters"); + return false; + } + } else { + MOT_LOG_TRACE("One-time filters not specified for inner-scan"); + } + return true; +} + +static MotJitContext* JitPointJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT Point JOIN query at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedPointJoin"); IssueDebugLog("Starting execution of jitted Point JOIN"); @@ -1078,25 +1125,27 @@ static JitContext* JitPointJoinCodegen(const Query* query, const char* query_str // initialize rows_processed local variable buildResetRowsProcessed(ctx); - // search the outer row - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); + // clear tuple even if row is not found later + AddExecClearTuple(ctx); + + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Point JOIN")) { return nullptr; } - // clear tuple even if row is not found later - AddExecClearTuple(ctx); + // search the outer row + if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("Point JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } // fetch row for read MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; llvm::Value* outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); // check for additional filters - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported outer scan filter"); - DestroyCodeGenContext(ctx); + if (!buildFilterRow(ctx, outer_row, nullptr, &plan->_outer_scan._filters, nullptr)) { + JIT_TRACE_FAIL("Point JOIN", "Unsupported outer scan filter"); return nullptr; } @@ -1106,17 +1155,15 @@ static JitContext* JitPointJoinCodegen(const Query* query, const char* query_str AddCopyOuterStateRow(ctx); // now search the inner row - if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_INNER, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); + if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, JIT_RANGE_SCAN_INNER, outer_row)) { + JIT_TRACE_FAIL("Point JOIN", "Unsupported inner WHERE clause type"); return nullptr; } llvm::Value* inner_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); // check for additional filters - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported inner scan filter"); - DestroyCodeGenContext(ctx); + if (!buildFilterRow(ctx, outer_row, inner_row, &plan->_inner_scan._filters, nullptr)) { + JIT_TRACE_FAIL("Point JOIN", "Unsupported inner scan filter"); return nullptr; } @@ -1124,10 +1171,9 @@ static JitContext* JitPointJoinCodegen(const Query* query, const char* query_str llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Point JOIN query: failed to select row columns into result tuple"); - DestroyCodeGenContext(ctx); + MOT_LOG_TRACE("Selecting row columns in the result tuple"); + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Point JOIN", "Failed to select row columns into result tuple"); return nullptr; } @@ -1142,41 +1188,120 @@ static JitContext* JitPointJoinCodegen(const Query* query, const char* query_str // signal to envelope executor scan ended (this is a point query) AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_POINT_JOIN); + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_POINT_JOIN, query_string, codegenStats); + return jitContext; +} - // cleanup - DestroyCodeGenContext(ctx); +static MotJitContext* JitPointLeftJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT Point LEFT JOIN query at thread %p", (void*)pthread_self()); - return jit_context; + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedPointLeftJoin"); + IssueDebugLog("Starting execution of jitted Point LEFT JOIN"); + + // initialize rows_processed local variable + buildResetRowsProcessed(ctx); + + // clear tuple even if row is not found later + AddExecClearTuple(ctx); + + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Point Left-JOIN")) { + return nullptr; + } + + // search the outer row + if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("Point Left-JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } + + // fetch row for read + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + llvm::Value* outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); + + // check for additional filters + if (!buildFilterRow(ctx, outer_row, nullptr, &plan->_outer_scan._filters, nullptr)) { + JIT_TRACE_FAIL("Point Left-JOIN", "Unsupported outer scan filter"); + return nullptr; + } + + // before we move on to inner point scan, we save the outer row in a safe copy (but for that purpose we need to save + // row in outer scan state) + AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); + AddCopyOuterStateRow(ctx); + + // now search the inner row + if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, JIT_RANGE_SCAN_INNER, outer_row)) { + JIT_TRACE_FAIL("Point Left-JOIN", "Unsupported inner WHERE clause type"); + return nullptr; + } + + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + + // attention: if the row is null we continue + llvm::Value* filterPassed = ctx->m_builder->CreateAlloca(ctx->INT32_T); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), filterPassed); + llvm::Value* inner_row = AddSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); + JIT_IF_BEGIN(check_inner_row_found) + JIT_IF_EVAL(inner_row); + { + // check for additional filters, if failed we jump to post block + IssueDebugLog("Inner row found"); + if (!buildFilterRow( + ctx, outer_row_copy, inner_row, &plan->_inner_scan._filters, JIT_IF_CURRENT()->GetPostBlock())) { + JIT_TRACE_FAIL("Point Left-JOIN", "Unsupported inner scan filter"); + return nullptr; + } + // raise flag that all filters passed + IssueDebugLog("Inner row PASSED filter test for LEFT JOIN"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(1), filterPassed); + } + JIT_IF_END() + + // select row columns (if row not found or filter failed, then select null values for inner row) + if (!BuildSelectLeftJoinRowColumns(ctx, outer_row_copy, plan, inner_row, filterPassed)) { + JIT_TRACE_FAIL("Point Left-JOIN", "Failed to select row columns into result tuple"); + return nullptr; + } + + AddExecStoreVirtualTuple(ctx); + + // update number of rows processed + buildIncrementRowsProcessed(ctx); + + // execute *tp_processed = rows_processed + AddSetTpProcessed(ctx); + + // signal to envelope executor scan ended (this is a point query) + AddSetScanEnded(ctx, 1); + + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + + // return success from calling function + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); + + // wrap up + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_POINT_JOIN, query_string, codegenStats); + return jitContext; } /** @brief Generates code for range JOIN query with outer point query and a possible LIMIT clause but without * aggregation. */ -static JitContext* JitPointOuterJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) +static MotJitContext* JitOuterPointJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT Outer Point JOIN query at thread %p", (void*)pthread_self()); - int max_arg = 0; - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedOuterPointJoin"); IssueDebugLog("Starting execution of jitted Outer Point JOIN"); @@ -1190,54 +1315,53 @@ static JitContext* JitPointOuterJoinCodegen(const Query* query, const char* quer // emit code to cleanup previous scan in case this is a new scan AddCleanupOldScan(ctx); + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Outer Point JOIN")) { + return nullptr; + } + // we first check if outer state row was already searched - llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + llvm::Value* outer_row = AddGetStateRow(ctx, JIT_RANGE_SCAN_MAIN); JIT_IF_BEGIN(check_outer_row_ready) - JIT_IF_EVAL_NOT(outer_row_copy) - // search the outer row - if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; + JIT_IF_EVAL_NOT(outer_row); + { + // search the outer row + if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("Outer Point JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } + + // fetch row for read and check for additional filters + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); + if (!buildFilterRow(ctx, outer_row, nullptr, &plan->_outer_scan._filters, nullptr)) { + JIT_TRACE_FAIL("Outer Point JOIN", "Unsupported outer scan filter"); + return nullptr; + } + + // before we move on to inner range scan, we save the outer row in a safe copy (but for that purpose we need to + // save row in outer scan state) + AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); + AddCopyOuterStateRow(ctx); } - - // fetch row for read - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - llvm::Value* outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner range scan, we save the outer row in a safe copy (but for that purpose we need to save - // row in outer scan state) - AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); - AddCopyOuterStateRow(ctx); - outer_row_copy = AddGetOuterStateRowCopy(ctx); // must get copy again, otherwise it is null JIT_IF_END() + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + // now prepare inner scan if needed, if no row was found then emit code to return from function (since outer scan is // a point query) MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; llvm::Value* inner_row = buildPrepareStateScanRow( - ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, &max_arg, outer_row_copy, nullptr, nullptr); + ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, outer_row_copy, nullptr, nullptr); if (inner_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Outer Point JOIN", "Unsupported inner WHERE clause type"); return nullptr; } - // retrieve the safe copy of the outer row - outer_row_copy = AddGetOuterStateRowCopy(ctx); - // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: failed to select row columns into " - "result tuple"); - DestroyCodeGenContext(ctx); + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Outer Point JOIN", "Failed to select row columns into result tuple"); return nullptr; } @@ -1255,40 +1379,117 @@ static JitContext* JitPointOuterJoinCodegen(const Query* query, const char* quer // execute *tp_processed = rows_processed AddSetTpProcessed(ctx); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; +} - // cleanup - DestroyCodeGenContext(ctx); +/** @brief Generates code for range JOIN query with outer point query and a possible LIMIT clause but without + * aggregation. */ +static MotJitContext* JitOuterPointLeftJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, + const char* query_string, JitJoinPlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT Outer Point LEFT JOIN query at thread %p", (void*)pthread_self()); - return jit_context; + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedOuterPointLeftJoin"); + IssueDebugLog("Starting execution of jitted Outer Point LEFT JOIN"); + + // initialize rows_processed local variable + buildResetRowsProcessed(ctx); + + // clear tuple even if row is not found later + AddExecClearTuple(ctx); + + // emit code to cleanup previous scan in case this is a new scan + AddCleanupOldScan(ctx); + + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Outer Point Left-JOIN")) { + return nullptr; + } + + // we first check if outer state row was already searched + llvm::Value* outer_row = AddGetStateRow(ctx, JIT_RANGE_SCAN_MAIN); + JIT_IF_BEGIN(check_outer_row_ready) + JIT_IF_EVAL_NOT(outer_row); + { + // search the outer row + if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("Outer Point Left-JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } + + // fetch row for read and check for additional filters + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); + if (!buildFilterRow(ctx, outer_row, nullptr, &plan->_outer_scan._filters, nullptr)) { + JIT_TRACE_FAIL("Outer Point Left-JOIN", "Unsupported outer scan filter"); + return nullptr; + } + + // before we move on to inner range scan, we save the outer row in a safe copy (but for that purpose we need to + // save row in outer scan state) + AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); + AddCopyOuterStateRow(ctx); + } + JIT_IF_END() + + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + + // now prepare inner scan if needed, if no row was found or failed to pass filter, then we continue (suppress emit + // return instruction), since in left join we report nulls + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + llvm::Value* inner_row = buildPrepareStateScanRow( + ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, outer_row_copy, nullptr, nullptr, false); + if (inner_row == nullptr) { + JIT_TRACE_FAIL("Outer Point Left-JOIN", "Unsupported inner WHERE clause type"); + return nullptr; + } + + // now begin selecting columns into result + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Outer Point Left-JOIN", "Failed to select row columns into result tuple"); + return nullptr; + } + + AddExecStoreVirtualTuple(ctx); + + // update number of rows processed + buildIncrementRowsProcessed(ctx); + + // clear inner row for next iteration + AddResetStateRow(ctx, JIT_RANGE_SCAN_INNER); + + // if a limit clause exists, then increment limit counter and check if reached limit + buildCheckLimit(ctx, plan->_limit_count); + + // execute *tp_processed = rows_processed + AddSetTpProcessed(ctx); + + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + + // return success from calling function + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); + + // wrap up + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; } /** @brief Generates code for range JOIN query with inner point query and a possible LIMIT clause but without * aggregation. */ -static JitContext* JitPointInnerJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) +static MotJitContext* JitInnerPointJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT Inner Point JOIN at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedInnerPointJoin"); IssueDebugLog("Starting execution of jitted inner point JOIN"); @@ -1302,16 +1503,19 @@ static JitContext* JitPointInnerJoinCodegen(const Query* query, const char* quer // emit code to cleanup previous scan in case this is a new scan AddCleanupOldScan(ctx); + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Inner Point JOIN")) { + return nullptr; + } + // prepare stateful scan if not done so already, if row not found then emit code to return from function (since this // is an outer scan) - int max_arg = 0; llvm::BasicBlock* fetch_outer_row_bb = nullptr; MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; llvm::Value* outer_row = buildPrepareStateScanRow( - ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, &fetch_outer_row_bb); + ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, nullptr, nullptr, &fetch_outer_row_bb); if (outer_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Inner Point JOIN", "Unsupported outer WHERE clause type"); return nullptr; } @@ -1319,28 +1523,24 @@ static JitContext* JitPointInnerJoinCodegen(const Query* query, const char* quer AddCopyOuterStateRow(ctx); // now search the inner row - if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_INNER, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); + if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, JIT_RANGE_SCAN_INNER, outer_row)) { + JIT_TRACE_FAIL("Inner Point JOIN", "Unsupported inner WHERE clause type"); return nullptr; } llvm::Value* inner_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); - // check for additional filters - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &max_arg, fetch_outer_row_bb)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported inner scan filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - // retrieve the safe copy of the outer row llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + // check for additional filters + if (!buildFilterRow(ctx, outer_row_copy, inner_row, &plan->_inner_scan._filters, fetch_outer_row_bb)) { + JIT_TRACE_FAIL("Inner Point JOIN", "Unsupported inner scan filter"); + return nullptr; + } + // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: failed to select row columns into " - "result tuple"); - DestroyCodeGenContext(ctx); + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Inner Point JOIN", "Failed to select row columns into result tuple"); return nullptr; } @@ -1356,39 +1556,118 @@ static JitContext* JitPointInnerJoinCodegen(const Query* query, const char* quer // generate code for setting output parameter tp_processed value to rows_processed AddSetTpProcessed(ctx); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; +} - // cleanup - DestroyCodeGenContext(ctx); +/** @brief Generates code for range JOIN query with inner point query and a possible LIMIT clause but without + * aggregation. */ +static MotJitContext* JitInnerPointLeftJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, + const char* query_string, JitJoinPlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT Inner Point Left JOIN at thread %p", (void*)pthread_self()); - return jit_context; + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedInnerPointLeftJoin"); + IssueDebugLog("Starting execution of jitted inner point LEFT JOIN"); + + // initialize rows_processed local variable + buildResetRowsProcessed(ctx); + + // clear tuple even if row is not found later + AddExecClearTuple(ctx); + + // emit code to cleanup previous scan in case this is a new scan + AddCleanupOldScan(ctx); + + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Inner Point Left-JOIN")) { + return nullptr; + } + + // prepare stateful scan if not done so already, if row not found then emit code to return from function (since this + // is an outer scan) + llvm::BasicBlock* fetch_outer_row_bb = nullptr; + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + llvm::Value* outer_row = buildPrepareStateScanRow( + ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, nullptr, nullptr, &fetch_outer_row_bb); + if (outer_row == nullptr) { + JIT_TRACE_FAIL("Inner Point Left-JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } + + // before we move on to inner scan, we save the outer row in a safe copy + AddCopyOuterStateRow(ctx); + + // now search the inner row + if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, JIT_RANGE_SCAN_INNER, outer_row)) { + JIT_TRACE_FAIL("Inner Point Left-JOIN", "Unsupported inner WHERE clause type"); + return nullptr; + } + + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + + // attention: if the row is null we continue + llvm::Value* filterPassed = ctx->m_builder->CreateAlloca(ctx->INT32_T); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), filterPassed); + llvm::Value* inner_row = AddSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); + JIT_IF_BEGIN(check_inner_row_found) + JIT_IF_EVAL(inner_row); + { + // check for additional filters, if failed we jump to post block + IssueDebugLog("Inner row found"); + if (!buildFilterRow( + ctx, outer_row_copy, inner_row, &plan->_inner_scan._filters, JIT_IF_CURRENT()->GetPostBlock())) { + JIT_TRACE_FAIL("Inner Point Left-JOIN", "Unsupported inner scan filter"); + return nullptr; + } + // raise flag that all filters passed + IssueDebugLog("Inner row PASSED filter test for LEFT JOIN"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(1), filterPassed); + } + JIT_IF_END() + + // select row columns (if row not found or filter failed, then select null values for inner row) + if (!BuildSelectLeftJoinRowColumns(ctx, outer_row_copy, plan, inner_row, filterPassed)) { + JIT_TRACE_FAIL("Inner Point Left-JOIN", "Failed to select row columns into result tuple"); + return nullptr; + } + + AddExecStoreVirtualTuple(ctx); + buildIncrementRowsProcessed(ctx); + + // make sure that next iteration find an empty state row, so scan makes a progress + AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); + + // if a limit clause exists, then increment limit counter and check if reached limit + buildCheckLimit(ctx, plan->_limit_count); + + // generate code for setting output parameter tp_processed value to rows_processed + AddSetTpProcessed(ctx); + + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + + // return success from calling function + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); + + // wrap up + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; } /** @brief Generates code for range JOIN query with a possible LIMIT clause but without aggregation. */ -static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) +static MotJitContext* JitRangeJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT range JOIN at thread %p", (void*)pthread_self()); - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, code_gen, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedRangeJoin"); IssueDebugLog("Starting execution of jitted range JOIN"); @@ -1402,15 +1681,18 @@ static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_str // emit code to cleanup previous scan in case this is a new scan AddCleanupOldScan(ctx); + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Range JOIN")) { + return nullptr; + } + // prepare stateful scan if not done so already - int max_arg = 0; llvm::BasicBlock* fetch_outer_row_bb = nullptr; MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; llvm::Value* outer_row = buildPrepareStateScanRow( - ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, &fetch_outer_row_bb); + ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, nullptr, nullptr, &fetch_outer_row_bb); if (outer_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Range JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Range JOIN", "Unsupported outer WHERE clause type"); return nullptr; } @@ -1419,10 +1701,9 @@ static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_str // now prepare inner scan if needed llvm::Value* inner_row = buildPrepareStateScanRow( - ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, &max_arg, outer_row, fetch_outer_row_bb, nullptr); + ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, outer_row, fetch_outer_row_bb, nullptr); if (inner_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Range JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Range JOIN", "Unsupported inner WHERE clause type"); return nullptr; } @@ -1430,10 +1711,8 @@ static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_str llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Range JOIN query: failed to select row columns into result tuple"); - DestroyCodeGenContext(ctx); + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Range JOIN", "Failed to select row columns into result tuple"); return nullptr; } @@ -1449,39 +1728,99 @@ static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_str // execute *tp_processed = rows_processed AddSetTpProcessed(ctx); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; +} - // cleanup - DestroyCodeGenContext(ctx); +/** @brief Generates code for range JOIN query with a possible LIMIT clause but without aggregation. */ +static MotJitContext* JitRangeLeftJoinCodegen(JitLlvmCodeGenContext* ctx, const Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) +{ + MOT_LOG_DEBUG("Generating code for MOT range LEFT JOIN at thread %p", (void*)pthread_self()); - return jit_context; + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedRangeLeftJoin"); + IssueDebugLog("Starting execution of jitted range LEFT JOIN"); + + // initialize rows_processed local variable + buildResetRowsProcessed(ctx); + + // clear tuple even if row is not found later + AddExecClearTuple(ctx); + + // emit code to cleanup previous scan in case this is a new scan + AddCleanupOldScan(ctx); + + // build one time filters if required + if (!BuildOneTimeJoinFilters(ctx, plan, "Range Left-JOIN")) { + return nullptr; + } + + // prepare stateful scan if not done so already + llvm::BasicBlock* fetch_outer_row_bb = nullptr; + MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; + llvm::Value* outer_row = buildPrepareStateScanRow( + ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, nullptr, nullptr, &fetch_outer_row_bb); + if (outer_row == nullptr) { + JIT_TRACE_FAIL("Range Left-JOIN", "Unsupported outer WHERE clause type"); + return nullptr; + } + + // before we move on to inner scan, we save the outer row in a safe copy + AddCopyOuterStateRow(ctx); + + // now prepare inner scan + // attention: if we fail (row not found, or did not pass filters) we still continue and report nulls to user + llvm::Value* inner_row = buildPrepareStateScanRow( + ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, outer_row, nullptr, nullptr, false); + if (inner_row == nullptr) { + JIT_TRACE_FAIL("Range Left-JOIN", "Unsupported inner WHERE clause type"); + return nullptr; + } + + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + + // now begin selecting columns into result + if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, inner_row)) { + JIT_TRACE_FAIL("Range Left-JOIN", "Failed to select row columns into result tuple"); + return nullptr; + } + + AddExecStoreVirtualTuple(ctx); + buildIncrementRowsProcessed(ctx); + + // clear inner row for next iteration + AddResetStateRow(ctx, JIT_RANGE_SCAN_INNER); + + // if a limit clause exists, then increment limit counter and check if reached limit + buildCheckLimit(ctx, plan->_limit_count); + + // execute *tp_processed = rows_processed + AddSetTpProcessed(ctx); + + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + + // return success from calling function + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); + + // wrap up + MotJitContext* jitContext = FinalizeCodegen(ctx, JIT_COMMAND_RANGE_JOIN, query_string, codegenStats); + return jitContext; } /** @brief Generates code for range JOIN query with an aggregator. */ -static JitContext* JitAggregateRangeJoinCodegen(Query* query, const char* query_string, JitJoinPlan* plan) +static MotJitContext* JitAggregateRangeJoinCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { MOT_LOG_DEBUG("Generating code for MOT aggregate range JOIN at thread %p", (void*)pthread_self()); - GsCodeGen* codeGen = SetupCodegenEnv(); - if (codeGen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(codeGen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, codeGen, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedAggregateRangeJoin"); IssueDebugLog("Starting execution of jitted aggregate range JOIN"); @@ -1493,163 +1832,183 @@ static JitContext* JitAggregateRangeJoinCodegen(Query* query, const char* query_ AddExecClearTuple(ctx); // prepare for aggregation - if (!prepareAggregate(ctx, &plan->_aggregate)) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: failed to prepare aggregate"); - DestroyCodeGenContext(ctx); + if (!prepareAggregates(ctx, plan->m_aggregates, plan->m_aggCount)) { + JIT_TRACE_FAIL("Aggregate Range JOIN", "Failed to prepare aggregates"); return nullptr; } - AddResetStateLimitCounter(ctx); + // counter for number of aggregates operates on a single row + llvm::Value* aggCount = ctx->m_builder->CreateAlloca(ctx->INT32_T, 0, nullptr, "agg_count"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount, true); + + // build one time filters if required + llvm::LLVMContext& context = ctx->m_codeGen->context(); + DEFINE_BLOCK(build_agg_join_result, ctx->m_jittedFunction); + if (!BuildOneTimeJoinFilters(ctx, plan, "Aggregate Range JOIN", build_agg_join_result)) { + return nullptr; + } // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call MOT::AccessType accessMode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; // begin the WHERE clause - int max_arg = 0; - // build range iterators MOT_LOG_DEBUG("Generating outer loop cursor for range JOIN query"); - JitLlvmRuntimeCursor outer_cursor = - buildRangeCursor(ctx, &plan->_outer_scan, &max_arg, JIT_RANGE_SCAN_MAIN, JIT_INDEX_SCAN_FORWARD, nullptr); + JitLlvmRuntimeCursor outer_cursor = buildRangeCursor(ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, nullptr); if (outer_cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported outer-loop WHERE clause type"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Aggregate Range JOIN", "Unsupported outer WHERE clause type"); return nullptr; } JIT_WHILE_BEGIN(cursor_aggregate_outer_loop) - llvm::BasicBlock* endOuterLoopBlock = JIT_WHILE_POST_BLOCK(); - llvm::Value* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &outer_cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - llvm::Value* outer_row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), accessMode, JIT_INDEX_SCAN_FORWARD, &outer_cursor, JIT_RANGE_SCAN_MAIN); + llvm::Value* res = AddIsScanEnd(ctx, plan->_outer_scan._scan_direction, &outer_cursor, JIT_RANGE_SCAN_MAIN); + JIT_WHILE_EVAL_NOT(res); + { + llvm::Value* outer_row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + accessMode, + plan->_outer_scan._scan_direction, + &outer_cursor, + JIT_RANGE_SCAN_MAIN); - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported outer-loop filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } + // check for additional filters, if not try to fetch next row + if (!buildFilterRow(ctx, outer_row, nullptr, &plan->_outer_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Aggregate Range JOIN", "Unsupported outer scan filter"); + return nullptr; + } - // before we move on to inner scan, we save the outer row in a safe copy (but for that purpose we need to save row - // in outer scan state) - AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); - AddCopyOuterStateRow(ctx); + // before we move on to inner scan, we save the outer row in a safe copy (but for that purpose we need to save + // row in outer scan state) + AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); + AddCopyOuterStateRow(ctx); - // now build the inner loop - MOT_LOG_DEBUG("Generating inner loop cursor for range JOIN query"); - JitLlvmRuntimeCursor inner_cursor = - buildRangeCursor(ctx, &plan->_inner_scan, &max_arg, JIT_RANGE_SCAN_INNER, JIT_INDEX_SCAN_FORWARD, outer_row); - if (inner_cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported inner-loop WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } + // now build the inner loop + MOT_LOG_DEBUG("Generating inner loop cursor for range JOIN query"); + JitLlvmRuntimeCursor inner_cursor = buildRangeCursor(ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, outer_row); + if (inner_cursor.begin_itr == nullptr) { + JIT_TRACE_FAIL("Aggregate Range JOIN", "Unsupported outer scan WHERE clause type"); + return nullptr; + } - JIT_WHILE_BEGIN(cursor_aggregate_inner_loop) - llvm::Value* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &inner_cursor, JIT_RANGE_SCAN_INNER); - JIT_WHILE_EVAL_NOT(res) - llvm::Value* inner_row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), accessMode, JIT_INDEX_SCAN_FORWARD, &inner_cursor, JIT_RANGE_SCAN_INNER); + JIT_WHILE_BEGIN(cursor_aggregate_inner_loop) + llvm::Value* resInner = + AddIsScanEnd(ctx, plan->_inner_scan._scan_direction, &inner_cursor, JIT_RANGE_SCAN_INNER); + JIT_WHILE_EVAL_NOT(resInner); + { + llvm::Value* inner_row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + accessMode, + plan->_inner_scan._scan_direction, + &inner_cursor, + JIT_RANGE_SCAN_INNER); - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported inner-loop filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } + // check for additional filters, if not try to fetch next row + if (!buildFilterRow(ctx, nullptr, inner_row, &plan->_inner_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Aggregate Range JOIN", "Unsupported inner scan filter"); + return nullptr; + } - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // find out to which table the aggreate expression refers, and aggregate it - // if row disqualified due to DISTINCT operator then go back to inner loop test block - bool aggRes = false; - if (plan->_aggregate._table == ctx->_inner_table_info.m_table) { - aggRes = buildAggregateRow(ctx, &plan->_aggregate, inner_row, JIT_WHILE_COND_BLOCK()); - } else { - // retrieve the safe copy of the outer row - llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); - aggRes = buildAggregateRow(ctx, &plan->_aggregate, outer_row_copy, JIT_WHILE_COND_BLOCK()); - } + // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) + // find out to which table the aggregate expression refers, and aggregate it + // if row disqualified due to DISTINCT operator then go back to inner loop test block + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount, true); + for (int i = 0; i < plan->m_aggCount; ++i) { + bool aggRes = false; + if (plan->m_aggregates[i]._table == ctx->_inner_table_info.m_table) { + aggRes = buildAggregateRow(ctx, &plan->m_aggregates[i], i, nullptr, inner_row, aggCount); + } else { + // retrieve the safe copy of the outer row + llvm::Value* outer_row_copy = AddGetOuterStateRowCopy(ctx); + aggRes = buildAggregateRow(ctx, &plan->m_aggregates[i], i, outer_row_copy, nullptr, aggCount); + } - if (!aggRes) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } + if (!aggRes) { + JIT_TRACE_FAIL("Aggregate Range JOIN", "Unsupported aggregate"); + return nullptr; + } + } + } + JIT_WHILE_END() // inner loop - // if a limit clause exists, then increment limit counter and check if reached limit - if (plan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - llvm::Value* currentLimitCount = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(currentLimitCount, JIT_CONST(plan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - AddDestroyCursor(ctx, &outer_cursor); + // cleanup + IssueDebugLog("Reached end of inner loop"); AddDestroyCursor(ctx, &inner_cursor); - ctx->_builder->CreateBr(endOuterLoopBlock); // break from inner outside of outer loop - JIT_IF_END() } - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of inner loop"); - AddDestroyCursor(ctx, &inner_cursor); - - JIT_WHILE_END() + JIT_WHILE_END() // outer loop // cleanup IssueDebugLog("Reached end of outer loop"); AddDestroyCursor(ctx, &outer_cursor); // wrap up aggregation and write to result tuple - buildAggregateResult(ctx, &plan->_aggregate); + JIT_GOTO(build_agg_join_result); + ctx->m_builder->SetInsertPoint(build_agg_join_result); + buildAggregateResult(ctx, plan->m_aggregates, plan->m_aggCount); // store the result tuple AddExecStoreVirtualTuple(ctx); // execute *tp_processed = rows_processed + buildIncrementRowsProcessed(ctx); // aggregate ALWAYS has at least one row processed AddSetTpProcessed(ctx); // signal to envelope executor scan ended AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), query_string, false); + // return success from calling function - builder.CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_AGGREGATE_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; + JitQueryContext* jitContext = + (JitQueryContext*)FinalizeCodegen(ctx, JIT_COMMAND_AGGREGATE_JOIN, query_string, codegenStats); + jitContext->m_aggCount = plan->m_aggCount; + return jitContext; } -static JitContext* JitJoinCodegen(Query* query, const char* query_string, JitJoinPlan* plan) +static MotJitContext* JitJoinCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitJoinPlan* plan, JitCodegenStats& codegenStats) { - JitContext* jit_context = nullptr; + MotJitContext* jit_context = nullptr; - if (plan->_aggregate._aggreaget_op == JIT_AGGREGATE_NONE) { + if (plan->m_aggCount == 0) { switch (plan->_scan_type) { case JIT_JOIN_SCAN_POINT: // special case: this is really a point query - jit_context = JitPointJoinCodegen(query, query_string, plan); + if (plan->_join_type == JitJoinType::JIT_JOIN_INNER) { + jit_context = JitPointJoinCodegen(ctx, query, query_string, plan, codegenStats); + } else if (plan->_join_type == JitJoinType::JIT_JOIN_LEFT) { + jit_context = JitPointLeftJoinCodegen(ctx, query, query_string, plan, codegenStats); + } break; case JIT_JOIN_SCAN_OUTER_POINT: // special case: outer scan is really a point query - jit_context = JitPointOuterJoinCodegen(query, query_string, plan); + if (plan->_join_type == JitJoinType::JIT_JOIN_INNER) { + jit_context = JitOuterPointJoinCodegen(ctx, query, query_string, plan, codegenStats); + } else if (plan->_join_type == JitJoinType::JIT_JOIN_LEFT) { + jit_context = JitOuterPointLeftJoinCodegen(ctx, query, query_string, plan, codegenStats); + } break; case JIT_JOIN_SCAN_INNER_POINT: // special case: inner scan is really a point query - jit_context = JitPointInnerJoinCodegen(query, query_string, plan); + if (plan->_join_type == JitJoinType::JIT_JOIN_INNER) { + jit_context = JitInnerPointJoinCodegen(ctx, query, query_string, plan, codegenStats); + } else if (plan->_join_type == JitJoinType::JIT_JOIN_LEFT) { + jit_context = JitInnerPointLeftJoinCodegen(ctx, query, query_string, plan, codegenStats); + } break; case JIT_JOIN_SCAN_RANGE: - jit_context = JitRangeJoinCodegen(query, query_string, plan); + if (plan->_join_type == JitJoinType::JIT_JOIN_INNER) { + jit_context = JitRangeJoinCodegen(ctx, query, query_string, plan, codegenStats); + } else if (plan->_join_type == JitJoinType::JIT_JOIN_LEFT) { + jit_context = JitRangeLeftJoinCodegen(ctx, query, query_string, plan, codegenStats); + } break; default: @@ -1658,7 +2017,7 @@ static JitContext* JitJoinCodegen(Query* query, const char* query_string, JitJoi break; } } else { - jit_context = JitAggregateRangeJoinCodegen(query, query_string, plan); + jit_context = JitAggregateRangeJoinCodegen(ctx, query, query_string, plan, codegenStats); } return jit_context; @@ -1673,10 +2032,8 @@ static bool JitSubSelectCodegen(JitLlvmCodeGenContext* ctx, JitCompoundPlan* pla JitSelectPlan* subPlan = (JitSelectPlan*)plan->_sub_query_plans[subQueryIndex]; // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan( - ctx, &subPlan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_SUB_QUERY, nullptr, -1, subQueryIndex)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: unsupported WHERE clause type"); + if (!buildPointScan(ctx, &subPlan->_query._search_exprs, JIT_RANGE_SCAN_SUB_QUERY, nullptr, -1, subQueryIndex)) { + JIT_TRACE_FAIL("Compound sub-SELECT", "Unsupported WHERE clause type"); return false; } @@ -1684,15 +2041,15 @@ static bool JitSubSelectCodegen(JitLlvmCodeGenContext* ctx, JitCompoundPlan* pla llvm::Value* row = buildSearchRow(ctx, MOT::AccessType::RD, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex); // check for additional filters - if (!buildFilterRow(ctx, row, &subPlan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: unsupported filter"); + if (!buildFilterRow(ctx, row, nullptr, &subPlan->_query._filters, nullptr)) { + JIT_TRACE_FAIL("Compound sub-SELECT", "Unsupported filter"); return false; } // now begin selecting columns into result IssueDebugLog("Selecting column into result"); - if (!selectRowColumns(ctx, row, &subPlan->_select_exprs, &max_arg, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: failed to process target entry"); + if (!selectRowColumns(ctx, row, &subPlan->_select_exprs)) { + JIT_TRACE_FAIL("Compound sub-SELECT", "Failed to process target entry"); return false; } @@ -1709,64 +2066,71 @@ static bool JitSubAggregateRangeSelectCodegen(JitLlvmCodeGenContext* ctx, JitCom JitRangeSelectPlan* subPlan = (JitRangeSelectPlan*)plan->_sub_query_plans[subQueryIndex]; // prepare for aggregation - if (!prepareAggregate(ctx, &subPlan->_aggregate)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range SELECT sub-query: failed to prepare aggregate"); + if (!prepareAggregates(ctx, subPlan->m_aggregates, subPlan->m_aggCount)) { + JIT_TRACE_FAIL("Aggregate Range sub-SELECT", "Failed to prepare aggregates"); return false; } - AddResetStateLimitCounter(ctx); + // counter for number of aggregates operates on a single row + llvm::Value* aggCount = ctx->m_builder->CreateAlloca(ctx->INT32_T, 0, nullptr, "agg_count"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount, true); + + // build one time filters if required + llvm::LLVMContext& context = ctx->m_codeGen->context(); + DEFINE_BLOCK(build_agg_result, ctx->m_jittedFunction); + if (subPlan->_index_scan.m_oneTimeFilters._filter_count > 0) { + if (!BuildOneTimeFilters(ctx, &subPlan->_index_scan.m_oneTimeFilters, build_agg_result)) { + JIT_TRACE_FAIL("Aggregate Range SELECT", "Failed to build one-time filters"); + return false; + } + } else { + MOT_LOG_TRACE("One-time filters not specified"); + } // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call - MOT::AccessType access_mode = MOT::AccessType::RD; + MOT::AccessType accessMode = MOT::AccessType::RD; // begin the WHERE clause - int max_arg = 0; JitIndexScanDirection index_scan_direction = JIT_INDEX_SCAN_FORWARD; // build range iterators MOT_LOG_DEBUG("Generating range cursor for range SELECT sub-query"); - JitLlvmRuntimeCursor cursor = buildRangeCursor( - ctx, &subPlan->_index_scan, &max_arg, JIT_RANGE_SCAN_SUB_QUERY, index_scan_direction, nullptr, subQueryIndex); + JitLlvmRuntimeCursor cursor = + buildRangeCursor(ctx, &subPlan->_index_scan, JIT_RANGE_SCAN_SUB_QUERY, nullptr, subQueryIndex); if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range SELECT sub-query: unsupported WHERE clause type"); + JIT_TRACE_FAIL("Aggregate Range sub-SELECT", "Unsupported WHERE clause type"); return false; } JIT_WHILE_BEGIN(cursor_aggregate_loop) llvm::Value* res = AddIsScanEnd(ctx, index_scan_direction, &cursor, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex); - JIT_WHILE_EVAL_NOT(res) - llvm::Value* row = buildGetRowFromIterator(ctx, - JIT_WHILE_POST_BLOCK(), - access_mode, - JIT_INDEX_SCAN_FORWARD, - &cursor, - JIT_RANGE_SCAN_SUB_QUERY, - subQueryIndex); + JIT_WHILE_EVAL_NOT(res); + { + llvm::Value* row = buildGetRowFromIterator(ctx, + JIT_WHILE_COND_BLOCK(), + JIT_WHILE_POST_BLOCK(), + accessMode, + subPlan->_index_scan._scan_direction, + &cursor, + JIT_RANGE_SCAN_SUB_QUERY, + subQueryIndex); - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, row, &subPlan->_index_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT sub-query: unsupported filter"); - return false; - } + // check for additional filters, if not try to fetch next row + if (!buildFilterRow(ctx, row, nullptr, &subPlan->_index_scan._filters, JIT_WHILE_COND_BLOCK())) { + JIT_TRACE_FAIL("Aggregate Range sub-SELECT", "Unsupported filter"); + return false; + } - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // if row disqualified due to DISTINCT operator then go back to loop test block - if (!buildAggregateRow(ctx, &subPlan->_aggregate, row, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT sub-query: unsupported aggregate"); - return false; - } - - // if a limit clause exists, then increment limit counter and check if reached limit - if (subPlan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - llvm::Value* current_limit_count = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(current_limit_count, JIT_CONST(subPlan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - JIT_WHILE_BREAK() // break from loop - JIT_IF_END() + // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) + // if row disqualified due to DISTINCT operator then go back to loop test block + MOT_ASSERT(subPlan->m_aggCount <= 1); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), aggCount, true); + for (int i = 0; i < subPlan->m_aggCount; ++i) { + if (!buildAggregateRow(ctx, &subPlan->m_aggregates[i], i, row, nullptr, aggCount)) { + JIT_TRACE_FAIL("Aggregate Range sub-SELECT", "Unsupported aggregate"); + return false; + } + } } JIT_WHILE_END() @@ -1775,7 +2139,9 @@ static bool JitSubAggregateRangeSelectCodegen(JitLlvmCodeGenContext* ctx, JitCom AddDestroyCursor(ctx, &cursor); // wrap up aggregation and write to result tuple (even though this is unfitting to outer query tuple...) - buildAggregateResult(ctx, &subPlan->_aggregate); + JIT_GOTO(build_agg_result); + ctx->m_builder->SetInsertPoint(build_agg_result); + buildAggregateResult(ctx, subPlan->m_aggregates, subPlan->m_aggCount); // coy aggregate result from outer query result tuple into sub-query result tuple AddCopyAggregateToSubQueryResult(ctx, subQueryIndex); @@ -1800,13 +2166,12 @@ static bool JitSubQueryCodeGen(JitLlvmCodeGenContext* ctx, JitCompoundPlan* plan return result; } -static JitContext* JitCompoundOuterSelectCodegen( - JitLlvmCodeGenContext* ctx, Query* query, const char* queryString, JitSelectPlan* plan) +static MotJitContext* JitCompoundOuterSelectCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* queryString, + JitSelectPlan* plan, JitCodegenStats& codegenStats) { // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: unsupported WHERE clause type"); + if (!buildPointScan(ctx, &plan->_query._search_exprs, JIT_RANGE_SCAN_MAIN, nullptr)) { + JIT_TRACE_FAIL("Compound Outer-SELECT", "Unsupported WHERE clause type"); return nullptr; } @@ -1815,15 +2180,15 @@ static JitContext* JitCompoundOuterSelectCodegen( llvm::Value* row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: unsupported filter"); + if (!buildFilterRow(ctx, row, nullptr, &plan->_query._filters, nullptr)) { + JIT_TRACE_FAIL("Compound Outer-SELECT", "Unsupported filter"); return nullptr; } // now begin selecting columns into result IssueDebugLog("Selecting columns into result"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: failed to process target entry"); + if (!selectRowColumns(ctx, row, &plan->_select_exprs)) { + JIT_TRACE_FAIL("Compound Outer-SELECT", "Failed to process target entry"); return nullptr; } @@ -1838,25 +2203,29 @@ static JitContext* JitCompoundOuterSelectCodegen( // signal to envelope executor scan ended (this is a point query) AddSetScanEnded(ctx, 1); + InjectProfileData(ctx, GetActiveNamespace(), queryString, false); + // return success from calling function - ctx->_builder->CreateRet(llvm::ConstantInt::get(ctx->INT32_T, (int)MOT::RC_OK, true)); + JIT_RETURN(JIT_CONST_INT32((int)MOT::RC_OK)); // wrap up - return FinalizeCodegen(ctx, max_arg, JIT_COMMAND_COMPOUND_SELECT); + return FinalizeCodegen(ctx, JIT_COMMAND_COMPOUND_SELECT, queryString, codegenStats); } -static JitContext* JitCompoundOuterCodegen( - JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, JitCompoundPlan* plan) +static MotJitContext* JitCompoundOuterCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* queryString, + JitCompoundPlan* plan, JitCodegenStats& codegenStats) { - JitContext* jitContext = nullptr; + MotJitContext* jitContext = nullptr; if (plan->_command_type == JIT_COMMAND_SELECT) { - jitContext = JitCompoundOuterSelectCodegen(ctx, query, query_string, (JitSelectPlan*)plan->_outer_query_plan); + jitContext = JitCompoundOuterSelectCodegen( + ctx, query, queryString, (JitSelectPlan*)plan->_outer_query_plan, codegenStats); } // currently other outer query types are not supported return jitContext; } -static JitContext* JitCompoundCodegen(Query* query, const char* query_string, JitCompoundPlan* plan) +static MotJitContext* JitCompoundCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitCompoundPlan* plan, JitCodegenStats& codegenStats) { // a compound query plan contains one or more sub-queries that evaluate to a datum that next needs to be fed as a // parameter to the outer query. We are currently imposing the following limitations: @@ -1871,20 +2240,6 @@ static JitContext* JitCompoundCodegen(Query* query, const char* query_string, Ji // sub-query result in step 1.1, according to sub-query index MOT_LOG_DEBUG("Generating code for MOT compound select at thread %p", (intptr_t)pthread_self()); - // prepare code generation context - GsCodeGen* code_gen = SetupCodegenEnv(); - if (code_gen == nullptr) { - return nullptr; - } - GsCodeGen::LlvmBuilder builder(code_gen->context()); - - JitLlvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_outer_query_plan->_query._table; - if (!InitCompoundCodeGenContext(&cg_ctx, code_gen, &builder, table, table->GetPrimaryIndex(), plan)) { - return nullptr; - } - JitLlvmCodeGenContext* ctx = &cg_ctx; - // prepare the jitted function (declare, get arguments into context and define locals) CreateJittedFunction(ctx, "MotJittedCompoundSelect"); IssueDebugLog("Starting execution of jitted COMPOUND SELECT"); @@ -1899,9 +2254,7 @@ static JitContext* JitCompoundCodegen(Query* query, const char* query_string, Ji JitSubLinkExpr* subLinkExpr = (JitSubLinkExpr*)plan->_outer_query_plan->_query._search_exprs._exprs[i]._expr; if (!JitSubQueryCodeGen(ctx, plan, subLinkExpr->_sub_query_index)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for COMPOUND SELECT query: Failed to generate code for sub-query"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Compound SELECT", "Failed to generate code for sub-query"); return nullptr; } ++subQueryCount; @@ -1912,46 +2265,48 @@ static JitContext* JitCompoundCodegen(Query* query, const char* query_string, Ji AddExecClearTuple(ctx); // generate code for the outer query - JitContext* jitContext = JitCompoundOuterCodegen(ctx, query, query_string, plan); + MotJitContext* jitContext = JitCompoundOuterCodegen(ctx, query, query_string, plan, codegenStats); if (jitContext == nullptr) { - MOT_LOG_TRACE("Failed to generate code for outer query in compound select"); - DestroyCodeGenContext(ctx); + JIT_TRACE_FAIL("Compound SELECT", "Failed to generate code for outer query"); return nullptr; } // prepare sub-query data in resulting JIT context (for later execution) MOT_ASSERT(subQueryCount > 0); MOT_ASSERT(subQueryCount == plan->_sub_query_count); - if ((subQueryCount > 0) && !PrepareSubQueryData(jitContext, plan)) { - MOT_LOG_TRACE("Failed to prepare tuple table slot array for sub-queries in JIT context object"); + if ((subQueryCount > 0) && !PrepareSubQueryData((JitQueryContext*)jitContext, plan)) { + JIT_TRACE_FAIL( + "Compound SELECT", "Failed to prepare tuple table slot array for sub-queries in JIT context object"); DestroyJitContext(jitContext); jitContext = nullptr; } - // cleanup - DestroyCodeGenContext(ctx); - return jitContext; } -static JitContext* JitRangeScanCodegen(Query* query, const char* query_string, JitRangeScanPlan* plan) +static MotJitContext* JitRangeScanCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitRangeScanPlan* plan, JitCodegenStats& codegenStats) { - JitContext* jit_context = nullptr; + MotJitContext* jit_context = nullptr; switch (plan->_command_type) { case JIT_COMMAND_UPDATE: - jit_context = JitRangeUpdateCodegen(query, query_string, (JitRangeUpdatePlan*)plan); + jit_context = JitRangeUpdateCodegen(ctx, query, query_string, (JitRangeUpdatePlan*)plan, codegenStats); break; case JIT_COMMAND_SELECT: { JitRangeSelectPlan* range_select_plan = (JitRangeSelectPlan*)plan; - if (range_select_plan->_aggregate._aggreaget_op == JIT_AGGREGATE_NONE) { - jit_context = JitRangeSelectCodegen(query, query_string, range_select_plan); + if (range_select_plan->m_aggCount == 0) { + jit_context = JitRangeSelectCodegen(ctx, query, query_string, range_select_plan, codegenStats); } else { - jit_context = JitAggregateRangeSelectCodegen(query, query_string, range_select_plan); + jit_context = JitAggregateRangeSelectCodegen(ctx, query, query_string, range_select_plan, codegenStats); } } break; + case JIT_COMMAND_DELETE: + jit_context = JitRangeDeleteCodegen(ctx, query, query_string, (JitRangeDeletePlan*)plan, codegenStats); + break; + default: MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Generate JIT Code", @@ -1963,21 +2318,21 @@ static JitContext* JitRangeScanCodegen(Query* query, const char* query_string, J return jit_context; } -static JitContext* JitPointQueryCodegen(Query* query, const char* query_string, JitPointQueryPlan* plan) +static MotJitContext* JitPointQueryCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* query_string, + JitPointQueryPlan* plan, JitCodegenStats& codegenStats) { - JitContext* jit_context = nullptr; - + MotJitContext* jitContext = nullptr; switch (plan->_command_type) { case JIT_COMMAND_UPDATE: - jit_context = JitUpdateCodegen(query, query_string, (JitUpdatePlan*)plan); + jitContext = JitUpdateCodegen(ctx, query, query_string, (JitUpdatePlan*)plan, codegenStats); break; case JIT_COMMAND_DELETE: - jit_context = JitDeleteCodegen(query, query_string, (JitDeletePlan*)plan); + jitContext = JitDeleteCodegen(ctx, query, query_string, (JitDeletePlan*)plan, codegenStats); break; case JIT_COMMAND_SELECT: - jit_context = JitSelectCodegen(query, query_string, (JitSelectPlan*)plan); + jitContext = JitSelectCodegen(ctx, query, query_string, (JitSelectPlan*)plan, codegenStats); break; default: @@ -1987,50 +2342,315 @@ static JitContext* JitPointQueryCodegen(Query* query, const char* query_string, (int)plan->_command_type); break; } - - return jit_context; + return jitContext; } -JitContext* JitCodegenLlvmQuery(Query* query, const char* query_string, JitPlan* plan) +static MotJitContext* JitInvokeCodegen(JitLlvmCodeGenContext* ctx, Query* query, const char* queryString, + JitInvokePlan* plan, JitCodegenStats& codegenStats) { - JitContext* jit_context = nullptr; + MOT_LOG_DEBUG("Generating code for invoke at thread %p", (void*)pthread_self()); - MOT_LOG_DEBUG("*** Attempting to generate planned LLVM-jitted code for query: %s", query_string); + // prepare the jitted function (declare, get arguments into context and define locals) + CreateJittedFunction(ctx, "MotJittedInvoke"); + IssueDebugLog("Starting execution of jitted INVOKE"); + IssueDebugLog("Preparing function call arguments"); + MOT_LOG_TRACE("Preparing %d function call arguments", plan->_arg_count); + + // prepare parameter list info + llvm::Value* paramListInfo = AddGetInvokeParamListInfo(ctx); + MOT_LOG_TRACE("Preparing %d plan arguments", plan->_arg_count); + for (int i = 0; i < plan->_arg_count; ++i) { + if (plan->_args[i]->_expr_type == JIT_EXPR_TYPE_PARAM) { + ctx->m_paramInfo[i].m_mode = JitParamMode::JIT_PARAM_DIRECT; + ctx->m_paramInfo[i].m_index = ((JitParamExpr*)plan->_args[i])->_param_id; + continue; + } + ctx->m_paramInfo[i].m_mode = JitParamMode::JIT_PARAM_COPY; + ctx->m_paramInfo[i].m_index = -1; + llvm::Value* expr = ProcessExpr(ctx, nullptr, nullptr, plan->_args[i]); + if (expr == nullptr) { + JIT_TRACE_FAIL("INVOKE", "Failed to process parameter expression"); + return nullptr; + } + llvm::Value* exprIsNull = AddGetExprIsNull(ctx); + AddSetParamValue(ctx, paramListInfo, JIT_CONST_INT32(i), plan->_args[i]->_result_type, expr, exprIsNull); + } + + // pass default parameters if needed + uint64_t paramCount = plan->m_functionPlan ? plan->m_functionPlan->m_argCount : get_func_nargs(plan->_function_id); + if (plan->m_defaultParamCount > 0) { + for (int i = 0; i < plan->m_defaultParamCount; ++i) { + llvm::Value* expr = ProcessExpr(ctx, nullptr, nullptr, plan->m_defaultParams[i]); + if (expr == nullptr) { + JIT_TRACE_FAIL("INVOKE", "Failed to process default value expression"); + return nullptr; + } + llvm::Value* exprIsNull = AddGetExprIsNull(ctx); + int paramPos = plan->_arg_count + i; + Oid resultType = plan->m_defaultParams[i]->_result_type; + AddSetParamValue(ctx, paramListInfo, JIT_CONST_INT32(paramPos), resultType, expr, exprIsNull); + ctx->m_paramInfo[paramPos].m_mode = JitParamMode::JIT_PARAM_COPY; + ctx->m_paramInfo[paramPos].m_index = -1; + } + } + + InjectInvokeProfileData(ctx, GetActiveNamespace(), queryString, true); + // invoke the stored procedure + llvm::Value* retValue = AddInvokeStoredProcedure(ctx); + InjectInvokeProfileData(ctx, GetActiveNamespace(), queryString, false); + + // NOTE: in case of SP1 calling SP2, where SP2 threw unhandled exception, then there is no need here to throw + // exception, because the sql code is pulled up into JIT context of SP1, where it is handled there + + InjectProfileData(ctx, GetActiveNamespace(), queryString, false); + + // return success from calling function + JIT_RETURN(retValue); + + // wrap up + JitQueryContext* jitContext = (JitQueryContext*)FinalizeCodegen(ctx, JIT_COMMAND_INVOKE, queryString, codegenStats); + if (jitContext != nullptr) { + // setup parameter info (for later preparing) - as much as the called function needs + MOT_LOG_TRACE("Installing %d parameters in source context (%d used)", paramCount, plan->_arg_count); + MOT_ASSERT((paramCount == 0 && ctx->m_paramInfo == nullptr) || (paramCount > 0 && ctx->m_paramInfo != nullptr)); + jitContext->m_invokeParamCount = paramCount; + jitContext->m_invokeParamInfo = ctx->m_paramInfo; + ctx->m_paramInfo = nullptr; + // compile the called stored procedure if there is such + if (plan->m_functionPlan != nullptr) { + jitContext->m_invokedQueryString = nullptr; + jitContext->m_invokedFunctionOid = InvalidOid; + jitContext->m_invokedFunctionTxnId = InvalidTransactionId; + jitContext->m_invokeContext = + (JitFunctionContext*)ProcessInvokedPlan(plan->m_functionPlan, JIT_CONTEXT_GLOBAL_SECONDARY); + if (jitContext->m_invokeContext == nullptr) { + MOT_LOG_TRACE("Failed to generate code for invoked stored procedure: %s", plan->_function_name); + DestroyJitContext(jitContext); + return nullptr; + } + jitContext->m_invokeContext->m_parentContext = jitContext; + } else { + jitContext->m_invokedQueryString = MakeInvokedQueryString(plan->_function_name, plan->_function_id); + if (jitContext->m_invokedQueryString == nullptr) { + MOT_LOG_TRACE("Failed to prepare invoked query string for stored procedure: %s", plan->_function_name); + DestroyJitContext(jitContext); + return nullptr; + } + jitContext->m_invokedFunctionOid = plan->_function_id; + jitContext->m_invokedFunctionTxnId = plan->m_functionTxnId; + jitContext->m_invokeContext = nullptr; + } + } + + return jitContext; +} + +#define JIT_ASSERT_QUERY_CODEGEN_UTIL_VALID() \ + do { \ + MOT_ASSERT(JIT_IF_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_WHILE_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_DO_WHILE_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_FOR_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_SWITCH_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_TRY_CURRENT() == nullptr); \ + } while (0) + +static bool InitCodeGenContextByPlan( + JitLlvmCodeGenContext* ctx, GsCodeGen* codeGen, GsCodeGen::LlvmBuilder* builder, JitPlan* plan) +{ + // ATTENTION: in case of failure the code-gen object is destroyed, otherwise it is owned by the context + bool result = false; switch (plan->_plan_type) { - case JIT_PLAN_INSERT_QUERY: - jit_context = JitInsertCodegen(query, query_string, (JitInsertPlan*)plan); + case JIT_PLAN_INSERT_QUERY: { + JitInsertPlan* insertPlan = (JitInsertPlan*)plan; + MOT::Table* table = insertPlan->_table; + result = InitCodeGenContext(ctx, codeGen, builder, table, table->GetPrimaryIndex()); break; + } - case JIT_PLAN_POINT_QUERY: - jit_context = JitPointQueryCodegen(query, query_string, (JitPointQueryPlan*)plan); + case JIT_PLAN_POINT_QUERY: { + JitPointQueryPlan* pqueryPlan = (JitPointQueryPlan*)plan; + MOT::Table* table = pqueryPlan->_query._table; + result = InitCodeGenContext(ctx, codeGen, builder, table, table->GetPrimaryIndex()); break; + } - case JIT_PLAN_RANGE_SCAN: - jit_context = JitRangeScanCodegen(query, query_string, (JitRangeScanPlan*)plan); + case JIT_PLAN_RANGE_SCAN: { + JitRangeScanPlan* rscanPlan = (JitRangeScanPlan*)plan; + MOT::Table* table = rscanPlan->_index_scan._table; + MOT::Index* index = rscanPlan->_index_scan._index; + result = InitCodeGenContext(ctx, codeGen, builder, table, index); break; + } - case JIT_PLAN_JOIN: - jit_context = JitJoinCodegen(query, query_string, (JitJoinPlan*)plan); + case JIT_PLAN_JOIN: { + JitJoinPlan* joinPlan = (JitJoinPlan*)plan; + MOT::Table* outerTable = joinPlan->_outer_scan._table; + MOT::Index* outerIndex = joinPlan->_outer_scan._index; + MOT::Table* innerTable = joinPlan->_inner_scan._table; + MOT::Index* innerIndex = joinPlan->_inner_scan._index; + result = InitCodeGenContext(ctx, codeGen, builder, outerTable, outerIndex, innerTable, innerIndex); break; + } - case JIT_PLAN_COMPOUND: - jit_context = JitCompoundCodegen(query, query_string, (JitCompoundPlan*)plan); + case JIT_PLAN_COMPOUND: { + JitCompoundPlan* compoundPlan = (JitCompoundPlan*)plan; + MOT::Table* table = compoundPlan->_outer_query_plan->_query._table; + result = InitCompoundCodeGenContext(ctx, codeGen, builder, table, table->GetPrimaryIndex(), compoundPlan); break; + } + + case JIT_PLAN_INVOKE: { + JitInvokePlan* invokePlan = (JitInvokePlan*)plan; + uint64_t paramCount = invokePlan->m_functionPlan ? invokePlan->m_functionPlan->m_argCount + : get_func_nargs(invokePlan->_function_id); + result = InitCodeGenContext(ctx, codeGen, builder, nullptr, nullptr, nullptr, nullptr, (int)paramCount); + break; + } default: MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "Generate JIT Code", "Invalid JIT plan type %d", (int)plan->_plan_type); + FreeGsCodeGen(codeGen); break; } - if (jit_context == nullptr) { + return result; +} + +MotJitContext* JitCodegenLlvmQuery(Query* query, const char* query_string, JitPlan* plan, JitCodegenStats& codegenStats) +{ + JIT_ASSERT_QUERY_CODEGEN_UTIL_VALID(); + GsCodeGen* codeGen = SetupCodegenEnv(); + if (codeGen == nullptr) { + return nullptr; + } + GsCodeGen::LlvmBuilder builder(codeGen->context()); + + volatile JitLlvmCodeGenContext ctx = {}; + if (!InitCodeGenContextByPlan((JitLlvmCodeGenContext*)&ctx, codeGen, &builder, plan)) { + // ATTENTION: in case of error code-gen object is destroyed already + MOT_LOG_TRACE("Failed to initialize code-gen context by plan"); + return nullptr; + } + ctx.m_queryString = query_string; + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile MotJitContext* jitContext = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + MOT_LOG_DEBUG("*** Attempting to generate planned LLVM-jitted code for query: %s", query_string); + PG_TRY(); + { + switch (plan->_plan_type) { + case JIT_PLAN_INSERT_QUERY: + jitContext = JitInsertCodegen( + (JitLlvmCodeGenContext*)&ctx, query, query_string, (JitInsertPlan*)plan, codegenStats); + break; + + case JIT_PLAN_POINT_QUERY: + jitContext = JitPointQueryCodegen( + (JitLlvmCodeGenContext*)&ctx, query, query_string, (JitPointQueryPlan*)plan, codegenStats); + break; + + case JIT_PLAN_RANGE_SCAN: + jitContext = JitRangeScanCodegen( + (JitLlvmCodeGenContext*)&ctx, query, query_string, (JitRangeScanPlan*)plan, codegenStats); + break; + + case JIT_PLAN_JOIN: + jitContext = + JitJoinCodegen((JitLlvmCodeGenContext*)&ctx, query, query_string, (JitJoinPlan*)plan, codegenStats); + break; + + case JIT_PLAN_COMPOUND: + jitContext = JitCompoundCodegen( + (JitLlvmCodeGenContext*)&ctx, query, query_string, (JitCompoundPlan*)plan, codegenStats); + break; + + case JIT_PLAN_INVOKE: + jitContext = JitInvokeCodegen( + (JitLlvmCodeGenContext*)&ctx, query, query_string, (JitInvokePlan*)plan, codegenStats); + break; + + default: + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Generate JIT Code", "Invalid JIT plan type %d", (int)plan->_plan_type); + break; + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + PrintErrorInfo(query_string); + } + PG_END_TRY(); + + // cleanup + DestroyCodeGenContext((JitLlvmCodeGenContext*)&ctx); + + if (jitContext == nullptr) { MOT_LOG_TRACE("Failed to generate LLVM-jitted code for query: %s", query_string); } else { MOT_LOG_DEBUG( - "Got LLVM-jitted function %p after compile, for query: %s", jit_context->m_llvmFunction, query_string); + "Got LLVM-jitted function %p after compile, for query: %s", jitContext->m_llvmFunction, query_string); } - return jit_context; + JIT_ASSERT_QUERY_CODEGEN_UTIL_VALID(); + return (MotJitContext*)jitContext; +} + +extern int JitExecLlvmQuery(JitQueryContext* jitContext, ParamListInfo params, TupleTableSlot* slot, + uint64_t* tuplesProcessed, int* scanEnded, int newScan) +{ + MOT_ASSERT(jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY); + JitQueryExecState* execState = (JitQueryExecState*)jitContext->m_execState; + + int result = 0; + + MOT_LOG_TRACE("Calling sigsetjmp on faultBuf %p, query: %s", execState->m_faultBuf, jitContext->m_queryString); + + // we setup a jump buffer in the execution state for fault handling + if (sigsetjmp(execState->m_faultBuf, 1) == 0) { + // execute the jitted-function + result = jitContext->m_llvmFunction(jitContext->m_table, + jitContext->m_index, + execState->m_searchKey, + execState->m_bitmapSet, + params, + slot, + tuplesProcessed, + scanEnded, + newScan, + execState->m_endIteratorKey, + jitContext->m_innerTable, + jitContext->m_innerIndex, + execState->m_innerSearchKey, + execState->m_innerEndIteratorKey); + } else { + uint64_t faultCode = execState->m_exceptionValue; + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT", + "Encountered run-time fault %" PRIu64 " (%s), exception status: %" PRIu64 ", exception value: %" PRIu64, + faultCode, + llvm_util::LlvmRuntimeFaultToString(faultCode), + execState->m_exceptionStatus, + execState->m_exceptionValue); + result = MOT::RC_JIT_SP_EXCEPTION; + } + + MOT_LOG_TRACE("JitExecLlvmQuery returned %d/%u for query: %s", result, jitContext->m_rc, jitContext->m_queryString); + return result; +} + +extern void JitLlvmResetCompileState() +{ + u_sess->mot_cxt.jit_llvm_if_stack = nullptr; + u_sess->mot_cxt.jit_llvm_loop_stack = nullptr; + u_sess->mot_cxt.jit_llvm_while_stack = nullptr; + u_sess->mot_cxt.jit_llvm_do_while_stack = nullptr; + u_sess->mot_cxt.jit_llvm_for_stack = nullptr; + u_sess->mot_cxt.jit_llvm_switch_case_stack = nullptr; + u_sess->mot_cxt.jit_llvm_try_catch_stack = nullptr; } } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.h index a504905d4..f3b5211b8 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_query_codegen.h @@ -33,19 +33,47 @@ namespace JitExec { // forward declarations -struct JitContext; +struct MotJitContext; struct JitPlan; /** * @brief Generates a native LLVM JIT-compiled code for the given query. * @param query The query to JIT-compile. * @param queryString The query string. - * @param analysis_result The information gathered during query analysis during a previous call to - * @ref IsJittable(). * @param plan The plan resulted from the analysis phase. + * @param[out] codegenStats Code generation statistics. * @return The resulting JIT context, or NULL if failed. */ -JitContext* JitCodegenLlvmQuery(Query* query, const char* queryString, JitPlan* plan); +MotJitContext* JitCodegenLlvmQuery(Query* query, const char* queryString, JitPlan* plan, JitCodegenStats& codegenStats); + +/** + * @brief Executed a previously LLVM-jitted query. + * @param jitContext The context produced by a previous call to @ref QueryCodegen(). + * @param params The list of bound parameters passed to the query. + * @param[out] slot The slot used for reporting select result. + * @param[out] tuplesProcessed The variable used to report the number of processed tuples. + * @param[out] scanEnded The variable used to report if a range scan ended. + * @param newScan Specifies whether this is a new scan or a continued previous scan. + * @return Zero if succeeded, otherwise an error code. + * @note This function may cause transaction abort. + */ +extern int JitExecLlvmQuery(JitQueryContext* jitContext, ParamListInfo params, TupleTableSlot* slot, + uint64_t* tuplesProcessed, int* scanEnded, int newScan); + +/** + * @brief Executed a previously LLVM-jitted stored procedure. + * @param jitContext The context produced by a previous call to @ref QueryCodegen(). + * @param params The list of bound parameters passed to the query. + * @param[out] slot The slot used for reporting select result. + * @param[out] tuplesProcessed The variable used to report the number of processed tuples. + * @param[out] scanEnded The variable used to report if a range scan ended. + * @return Zero if succeeded, otherwise an error code. + * @note This function may cause transaction abort. + */ +extern int JitExecLlvmFunction(JitFunctionContext* jitContext, ParamListInfo params, TupleTableSlot* slot, + uint64_t* tuplesProcessed, int* scanEnded); + +extern void JitLlvmResetCompileState(); } // namespace JitExec #endif /* JIT_LLVM_QUERY_CODEGEN_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.cpp b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.cpp new file mode 100644 index 000000000..25741501d --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.cpp @@ -0,0 +1,5560 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_llvm_sp.cpp + * LLVM-jitted stored-procedure execution. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.cpp + * + * ------------------------------------------------------------------------- + */ +/* + * ATTENTION: Be sure to include jit_llvm_sp.h before anything else because of gscodegen.h + * See jit_llvm_sp.h for more details. + */ +#include "jit_llvm_sp.h" +#include "global.h" +#include "postgres.h" +#include "catalog/pg_operator.h" +#include "utils/fmgroids.h" +#include "nodes/parsenodes.h" +#include "storage/ipc.h" +#include "nodes/pg_list.h" +#include "utils/elog.h" +#include "utils/numeric.h" +#include "utils/numeric_gs.h" +#include "catalog/pg_aggregate.h" +#include "executor/spi.h" +#include "nodes/nodeFuncs.h" +#include "utils/syscache.h" +#include "storage/mot/jit_exec.h" +#include "knl/knl_session.h" +#include "parser/parse_coerce.h" +#include "utils/plpgsql.h" +#include "fmgr.h" +#include "funcapi.h" + +#include "jit_util.h" +#include "jit_source_map.h" + +#include "mot_engine.h" +#include "utilities.h" +#include "debug_utils.h" +#include "jit_plan.h" +#include "jit_plan_sp.h" +#include "mm_global_api.h" +#include "jit_llvm_util.h" +#include "jit_llvm_funcs.h" +#include "jit_profiler.h" + +namespace JitExec { +DECLARE_LOGGER(JitLlvmSp, JitExec) + +static bool ProcessFunctionAction(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_function* function); +static bool ProcessStatementBlock(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_block* stmt); +static bool ProcessStatementAssign(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_assign* stmt); +static bool ProcessStatementIf(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_if* stmt); +static bool ProcessStatementGoto(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_goto* stmt); +static bool ProcessStatementCase(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_case* stmt); +static bool ProcessStatementLoop(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_loop* stmt); +static bool ProcessStatementWhile(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_while* stmt); +static bool ProcessStatementForI(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fori* stmt); +static bool ProcessStatementForS(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fors* stmt); +static bool ProcessStatementForC(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_forc* stmt); +static bool ProcessStatementForEach(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_foreach_a* stmt); +static bool ProcessStatementExit(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_exit* stmt); +static bool ProcessStatementReturn(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return* stmt); +static bool ProcessStatementReturnNext(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return_next* stmt); +static bool ProcessStatementReturnQuery(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return_query* stmt); +static bool ProcessStatementRaise(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_raise* stmt); +static bool ProcessStatementExecSql(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_execsql* stmt); +static bool ProcessStatementPerform(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_perform* stmt); +static bool ProcessStatementDynExecute(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_dynexecute* stmt); +static bool ProcessStatementDynForS(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_dynfors* stmt); +static bool ProcessStatementGetDiag(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_getdiag* stmt); +static bool ProcessStatementOpen(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_open* stmt); +static bool ProcessStatementFetch(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fetch* stmt); +static bool ProcessStatementClose(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_close* stmt); + +static llvm::Value* GetLocalOrParamByVarNo(JitLlvmFunctionCodeGenContext* ctx, int varno); +static llvm::Value* GetIsNullLocalOrParamByVarNo(JitLlvmFunctionCodeGenContext* ctx, int varno); +static bool ProcessStatementLabel(JitLlvmFunctionCodeGenContext* ctx, const char* label, const char* defaultName); +static llvm::Value* ProcessExpr(JitLlvmFunctionCodeGenContext* ctx, Expr* expr, Oid* resultType); +static bool IsSimpleExprStatic(JitLlvmFunctionCodeGenContext* ctx, Expr* expr); +static bool DefineBlockLocalVar(JitLlvmFunctionCodeGenContext* ctx, int varIndex); +static llvm::Value* ExecSubQuery(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, int tcount, bool strict, + bool mod, bool into, int* subQueryId, bool isPerform = false); +static llvm::Value* ProcessExpr(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, Oid* resultType); +static bool AssignValue(JitLlvmFunctionCodeGenContext* ctx, int varNo, PLpgSQL_expr* expr); +static void CheckThrownException(JitLlvmFunctionCodeGenContext* ctx); + +static llvm::Value* AddGetExprIsNull(JitLlvmFunctionCodeGenContext* ctx); + +static bool AddSetSqlStateAndErrorMessage(JitLlvmFunctionCodeGenContext* ctx, const char* errorMessage, int sqlState); + +static bool AssignScalarValue(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_var* target, llvm::Value* assignee, + llvm::Value* assigneeIsNull, llvm::Value* value, llvm::Value* isNull, Oid sourceType); + +#ifdef IssueDebugLog +#undef IssueDebugLog +#endif + +#ifdef MOT_JIT_DEBUG +inline llvm::Value* StringToValue(JitLlvmFunctionCodeGenContext* ctx, const char* str) +{ + llvm::Value* strVal = ctx->m_builder->CreateGlobalStringPtr(str); + return strVal; +} + +inline llvm::Value* ValueToInt32(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* value) +{ + llvm::Value* int32Val = value; + if (value->getType()->getIntegerBitWidth() < 32) { + int32Val = ctx->m_builder->CreateCast(llvm::Instruction::CastOps::SExt, value, ctx->INT32_T); + } else if (value->getType()->getIntegerBitWidth() > 32) { + int32Val = ctx->m_builder->CreateCast(llvm::Instruction::CastOps::Trunc, value, ctx->INT32_T); + } + return int32Val; +} + +inline void IssueDebugLogImpl(JitLlvmFunctionCodeGenContext* ctx, const char* function, const char* msg) +{ + llvm::Value* functionPtr = StringToValue(ctx, function); + llvm::Value* msgPtr = StringToValue(ctx, msg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_debugLogFunc, functionPtr, msgPtr, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_debugLogFunc, functionPtr, msgPtr, nullptr); + } +} + +inline void IssueDebugLogIntVarImpl(JitLlvmFunctionCodeGenContext* ctx, const char* msg, llvm::Value* arg) +{ + llvm::Value* msgPtr = StringToValue(ctx, msg); + llvm::Value* argInt32Val = ValueToInt32(ctx, arg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_debugLogIntFunc, msgPtr, argInt32Val, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_debugLogIntFunc, msgPtr, argInt32Val, nullptr); + } +} + +inline void IssueDebugLogIntImpl(JitLlvmFunctionCodeGenContext* ctx, const char* msg, int arg) +{ + IssueDebugLogIntVarImpl(ctx, msg, JIT_CONST_INT32(arg)); +} + +inline void IssueDebugLogStringImpl(JitLlvmFunctionCodeGenContext* ctx, const char* msg, const char* arg) +{ + llvm::Value* msgPtr = StringToValue(ctx, msg); + llvm::Value* argPtr = StringToValue(ctx, arg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_debugLogStringFunc, msgPtr, argPtr, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_debugLogStringFunc, msgPtr, argPtr, nullptr); + } +} + +inline void IssueDebugLogStringDatumVarImpl(JitLlvmFunctionCodeGenContext* ctx, const char* msg, llvm::Value* arg) +{ + llvm::Value* msgPtr = StringToValue(ctx, msg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_debugLogStringDatumFunc, msgPtr, arg, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_debugLogStringDatumFunc, msgPtr, arg, nullptr); + } +} + +inline void IssueDebugLogStringDatumImpl(JitLlvmFunctionCodeGenContext* ctx, const char* msg, Datum arg) +{ + IssueDebugLogStringDatumVarImpl(ctx, msg, JIT_CONST_INT64(arg)); +} + +inline void IssueDebugLogDatumImpl( + JitLlvmFunctionCodeGenContext* ctx, const char* msg, llvm::Value* datum, llvm::Value* isNull, int type) +{ + llvm::Value* msgPtr = StringToValue(ctx, msg); + llvm::ConstantInt* typeValue = JIT_CONST_INT32(type); + llvm::Value* isNullInt32Val = ValueToInt32(ctx, isNull); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_debugLogDatumFunc, msgPtr, datum, isNullInt32Val, typeValue, nullptr); + } else { + llvm_util::InsertFunctionCall( + terminator, ctx, ctx->m_debugLogDatumFunc, msgPtr, datum, isNullInt32Val, typeValue, nullptr); + } +} + +#define JIT_DEBUG_LOG(msg) IssueDebugLogImpl((JitLlvmFunctionCodeGenContext*)ctx, __FUNCTION__, msg) +#define JIT_DEBUG_LOG_INT(msg, arg) IssueDebugLogIntImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, arg) +#define JIT_DEBUG_LOG_INT_VAR(msg, arg) IssueDebugLogIntVarImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, arg) +#define JIT_DEBUG_LOG_STRING(msg, arg) IssueDebugLogStringImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, arg) +#define JIT_DEBUG_LOG_STRING_DATUM(msg, arg) IssueDebugLogStringDatumImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, arg) +#define JIT_DEBUG_LOG_STRING_DATUM_VAR(msg, arg) \ + IssueDebugLogStringDatumVarImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, arg) +#define JIT_DEBUG_LOG_DATUM(msg, datum, isNull, type) \ + IssueDebugLogDatumImpl((JitLlvmFunctionCodeGenContext*)ctx, msg, datum, isNull, type) + +#define JIT_DEBUG_LOG_VARNO(varNo) \ + do { \ + PLpgSQL_datum* target = ctx->_compiled_function->datums[varNo]; \ + if (target->dtype == PLPGSQL_DTYPE_VAR) { \ + JIT_DEBUG_LOG_INT("Scalar Varno %d", varNo); \ + llvm::Value* value = GetLocalOrParamByVarNo(ctx, varNo); \ + llvm::Value* valueIsNull = GetIsNullLocalOrParamByVarNo(ctx, varNo); \ + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(ctx->DATUM_T, value, true); \ + llvm::Value* loadedIsNull = ctx->m_builder->CreateLoad(ctx->BOOL_T, valueIsNull, true); \ + JIT_DEBUG_LOG_DATUM( \ + "Scalar Varno value", loadedValue, loadedIsNull, ((PLpgSQL_var*)target)->datatype->typoid); \ + } else { \ + JIT_DEBUG_LOG_INT("Non-scalar varno %d", varNo); \ + } \ + } while (0) + +#else +#define JIT_DEBUG_LOG(msg) +#define JIT_DEBUG_LOG_INT(msg, arg) +#define JIT_DEBUG_LOG_INT_VAR(msg, arg) +#define JIT_DEBUG_LOG_STRING(msg, arg) +#define JIT_DEBUG_LOG_STRING_DATUM(msg, arg) +#define JIT_DEBUG_LOG_STRING_DATUM_VAR(msg, arg) +#define JIT_DEBUG_LOG_DATUM(msg, datum, isNull, type) +#define JIT_DEBUG_LOG_VARNO(varNo) +#endif + +#ifdef MOT_JIT_DEBUG +inline void DefineDebugLog(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_debugLogFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "debugLog", ctx->STR_T, ctx->STR_T, nullptr); +} +inline void DefineDebugLogInt(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_debugLogIntFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "debugLogInt", ctx->STR_T, ctx->INT32_T, nullptr); +} +inline void DefineDebugLogString(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_debugLogStringFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "debugLogString", ctx->STR_T, ctx->STR_T, nullptr); +} +inline void DefineDebugLogStringDatum(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_debugLogStringDatumFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "debugLogStringDatum", ctx->STR_T, ctx->INT64_T, nullptr); +} +inline void DefineDebugLogDatum(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_debugLogDatumFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "debugLogDatum", ctx->STR_T, ctx->DATUM_T, ctx->INT32_T, ctx->INT32_T, nullptr); +} +#endif + +inline void DefineGetCurrentSubTransactionId(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getCurrentSubTransactionIdFunc = + llvm_util::DefineFunction(module, ctx->INT64_T, "JitGetCurrentSubTransactionId", nullptr); +} + +inline void DefineBeginBlockWithExceptions(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_beginBlockWithExceptionsFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitBeginBlockWithExceptions", nullptr); +} + +inline void DefineEndBlockWithExceptions(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_endBlockWithExceptionsFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitEndBlockWithExceptions", nullptr); +} + +inline void DefineCleanupBlockAfterException(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_cleanupBlockAfterExceptionFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitCleanupBlockAfterException", nullptr); +} + +inline void DefineGetExceptionOrigin(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getExceptionOriginFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "JitGetExceptionOrigin", nullptr); +} + +inline void DefineSetExceptionOrigin(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setExceptionOriginFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitSetExceptionOrigin", ctx->INT32_T, nullptr); +} + +inline void DefineCleanupBeforeReturn(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_cleanupBeforeReturnFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitCleanupBeforeReturn", ctx->INT64_T, nullptr); +} + +inline void DefineConvertViaString(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_convertViaStringFunc = llvm_util::DefineFunction( + module, ctx->DATUM_T, "JitConvertViaString", ctx->DATUM_T, ctx->INT32_T, ctx->INT32_T, ctx->INT32_T, nullptr); +} + +inline void DefineCastValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_castValueFunc = llvm_util::DefineFunction(module, + ctx->DATUM_T, + "JitCastValue", + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineSaveErrorInfo(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_saveErrorInfoFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "JitSaveErrorInfo", ctx->DATUM_T, ctx->INT32_T, ctx->DATUM_T, nullptr); +} + +inline void DefineGetErrorMessage(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getErrorMessageFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "JitGetErrorMessage", nullptr); +} + +inline void DefineGetSqlState(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSqlStateFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "JitGetSqlState", nullptr); +} + +inline void DefineGetSqlStateString(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSqlStateStringFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "JitGetSqlStateString", nullptr); +} + +inline void DefineGetDatumIsNotNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getDatumIsNotNullFunc = + llvm_util::DefineFunction(module, ctx->DATUM_T, "JitGetDatumIsNotNull", ctx->INT32_T, nullptr); +} + +inline void DefineGetExprIsNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getExprIsNullFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "GetExprIsNull", nullptr); +} + +inline void DefineSetExprIsNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setExprIsNullFunc = llvm_util::DefineFunction(module, ctx->VOID_T, "SetExprIsNull", ctx->INT32_T, nullptr); +} + +inline void DefineGetExprCollation(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getExprCollationFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "GetExprCollation", nullptr); +} + +inline void DefineSetExprCollation(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setExprCollationFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "SetExprCollation", ctx->INT32_T, nullptr); +} + +inline void DefineExecClearTuple(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_execClearTupleFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "execClearTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); +} + +inline void DefineExecStoreVirtualTuple(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_execStoreVirtualTupleFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "execStoreVirtualTuple", ctx->TupleTableSlotType->getPointerTo(), nullptr); +} + +inline void DefineGetParamAtRef(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getParamAtRefFunc = llvm_util::DefineFunction(module, + ctx->DATUM_T->getPointerTo(), + "JitGetParamAtRef", + ctx->ParamListInfoDataType->getPointerTo(), + ctx->INT32_T, + nullptr); +} + +inline void DefineIsParamNullRef(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_isParamNullRefFunc = llvm_util::DefineFunction(module, + ctx->BOOL_T->getPointerTo(), + "JitIsParamNullRef", + ctx->ParamListInfoDataType->getPointerTo(), + ctx->INT32_T, + nullptr); +} + +inline void DefineIsCompositeResult(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_isCompositeResultFunc = llvm_util::DefineFunction( + module, ctx->INT32_T, "IsCompositeResult", ctx->TupleTableSlotType->getPointerTo(), nullptr); +} + +inline void DefineCreateResultDatums(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_createResultDatumsFunc = + llvm_util::DefineFunction(module, ctx->DATUM_T->getPointerTo(), "CreateResultDatums", nullptr); +} + +inline void DefineCreateResultNulls(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_createResultNullsFunc = + llvm_util::DefineFunction(module, ctx->INT8_T->getPointerTo(), "CreateResultNulls", nullptr); +} + +inline void DefineSetResultValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setResultValueFunc = llvm_util::DefineFunction(module, + ctx->VOID_T, + "SetResultValue", + ctx->DATUM_T->getPointerTo(), + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineCreateResultHeapTuple(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_createResultHeapTupleFunc = llvm_util::DefineFunction(module, + ctx->DATUM_T, + "CreateResultHeapTuple", + ctx->DATUM_T->getPointerTo(), + ctx->INT8_T->getPointerTo(), + nullptr); +} + +inline void DefineSetSlotValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setSlotValueFunc = llvm_util::DefineFunction(module, + ctx->VOID_T, + "SetSlotValue", + ctx->TupleTableSlotType->getPointerTo(), + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineSetSPSubQueryParamValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setSPSubQueryParamValueFunc = llvm_util::DefineFunction(module, + ctx->VOID_T, + "SetSPSubQueryParamValue", + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineExecuteSubQuery(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_executeSubQueryFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "JitExecSubQuery", ctx->INT32_T, ctx->INT32_T, nullptr); +} + +inline void DefineReleaseNonJitSubQueryResources(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_releaseNonJitSubQueryResourcesFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitReleaseNonJitSubQueryResources", ctx->INT32_T, nullptr); +} + +inline void DefineGetTuplesProcessed(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getTuplesProcessedFunc = + llvm_util::DefineFunction(module, ctx->INT32_T, "JitGetTuplesProcessed", ctx->INT32_T, nullptr); +} + +inline void DefineGetSubQuerySlotValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSubQuerySlotValueFunc = + llvm_util::DefineFunction(module, ctx->DATUM_T, "JitGetSubQuerySlotValue", ctx->INT32_T, ctx->INT32_T, nullptr); +} + +inline void DefineGetSubQuerySlotIsNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSubQuerySlotIsNullFunc = llvm_util::DefineFunction( + module, ctx->INT32_T, "JitGetSubQuerySlotIsNull", ctx->INT32_T, ctx->INT32_T, nullptr); +} + +inline void DefineGetSubQueryResultHeapTuple(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSubQueryResultHeapTupleFunc = llvm_util::DefineFunction( + module, ctx->INT8_T->getPointerTo(), "JitGetSubQueryResultHeapTuple", ctx->INT32_T, nullptr); +} + +inline void DefineGetHeapTupleValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getHeapTupleValueFunc = llvm_util::DefineFunction(module, + ctx->DATUM_T, + "JitGetHeapTupleValue", + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->INT32_T->getPointerTo(), + nullptr); +} + +inline void DefineGetSpiResult(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getSpiResultFunc = llvm_util::DefineFunction(module, ctx->INT32_T, "JitGetSpiResult", ctx->INT32_T, nullptr); +} + +inline void DefineSetTpProcessed(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setTpProcessedFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setTpProcessed", ctx->INT64_T->getPointerTo(), ctx->INT64_T, nullptr); +} + +inline void DefineSetScanEnded(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_setScanEndedFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "setScanEnded", ctx->INT32_T->getPointerTo(), ctx->INT32_T, nullptr); +} + +inline void DefineGetConstAt(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_getConstAtFunc = llvm_util::DefineFunction(module, ctx->DATUM_T, "GetConstAt", ctx->INT32_T, nullptr); +} + +inline void DefineAbortFunction(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_abortFunctionFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "JitAbortFunction", ctx->INT32_T, nullptr); +} + +inline void DefineLlvmClearExceptionStack(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmClearExceptionStackFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "LlvmClearExceptionStack", nullptr); +} + +inline void DefineEmitProfileData(JitLlvmFunctionCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_emitProfileDataFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "EmitProfileData", ctx->INT32_T, ctx->INT32_T, ctx->INT32_T, nullptr); +} + +/** @brief Define all LLVM prototypes. */ +void InitCodeGenContextFuncs(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::Module* module = ctx->m_codeGen->module(); + + // define all function calls +#ifdef MOT_JIT_DEBUG + DefineDebugLog(ctx, module); + DefineDebugLogInt(ctx, module); + DefineDebugLogString(ctx, module); + DefineDebugLogStringDatum(ctx, module); + DefineDebugLogDatum(ctx, module); +#endif + DefineGetCurrentSubTransactionId(ctx, module); + DefineBeginBlockWithExceptions(ctx, module); + DefineEndBlockWithExceptions(ctx, module); + DefineCleanupBlockAfterException(ctx, module); + DefineGetExceptionOrigin(ctx, module); + DefineSetExceptionOrigin(ctx, module); + DefineCleanupBeforeReturn(ctx, module); + DefineConvertViaString(ctx, module); + DefineCastValue(ctx, module); + DefineSaveErrorInfo(ctx, module); + DefineGetErrorMessage(ctx, module); + DefineGetSqlState(ctx, module); + DefineGetSqlStateString(ctx, module); + DefineGetDatumIsNotNull(ctx, module); + DefineGetExprIsNull(ctx, module); + DefineSetExprIsNull(ctx, module); + DefineGetExprCollation(ctx, module); + DefineSetExprCollation(ctx, module); + DefineExecClearTuple(ctx, module); + DefineExecStoreVirtualTuple(ctx, module); + DefineGetParamAtRef(ctx, module); + DefineIsParamNullRef(ctx, module); + DefineIsCompositeResult(ctx, module); + DefineCreateResultDatums(ctx, module); + DefineCreateResultNulls(ctx, module); + DefineSetResultValue(ctx, module); + DefineCreateResultHeapTuple(ctx, module); + DefineSetSlotValue(ctx, module); + DefineSetSPSubQueryParamValue(ctx, module); + DefineExecuteSubQuery(ctx, module); + DefineReleaseNonJitSubQueryResources(ctx, module); + DefineGetTuplesProcessed(ctx, module); + DefineGetSubQuerySlotValue(ctx, module); + DefineGetSubQuerySlotIsNull(ctx, module); + DefineGetSubQueryResultHeapTuple(ctx, module); + DefineGetHeapTupleValue(ctx, module); + DefineGetSpiResult(ctx, module); + DefineSetTpProcessed(ctx, module); + DefineSetScanEnded(ctx, module); + DefineGetConstAt(ctx, module); + DefineAbortFunction(ctx, module); + DefineLlvmClearExceptionStack(ctx, module); + DefineEmitProfileData(ctx, module); +} + +/** @brief Define all LLVM used types synonyms. */ +void InitCodeGenContextTypes(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::LLVMContext& context = ctx->m_codeGen->context(); + + // PG types + ctx->ParamListInfoDataType = llvm::StructType::create(context, "ParamListInfoData"); + ctx->TupleTableSlotType = llvm::StructType::create(context, "TupleTableSlot"); + ctx->NumericDataType = llvm::StructType::create(context, "NumericData"); + ctx->VarCharType = llvm::StructType::create(context, "VarChar"); + ctx->BpCharType = llvm::StructType::create(context, "BpChar"); + + // MOT types + ctx->RowType = llvm::StructType::create(context, "Row"); +} + +/** @brief Initializes a context for compilation. */ +static bool InitLlvmFunctionCodeGenContext(JitLlvmFunctionCodeGenContext* ctx, GsCodeGen* codeGen, + GsCodeGen::LlvmBuilder* builder, PLpgSQL_function* function, ReturnSetInfo* returnSetInfo) +{ + llvm_util::InitLlvmCodeGenContext(ctx, codeGen, builder); + InitCodeGenContextTypes(ctx); + InitCodeGenContextFuncs(ctx); + + ctx->m_constCount = 0; + size_t allocSize = sizeof(Const) * MOT_JIT_MAX_CONST; + ctx->m_constValues = (Const*)MOT::MemSessionAlloc(allocSize); + if (ctx->m_constValues == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for constant array during code-generation context", + allocSize); + return false; + } + + allocSize = sizeof(bool) * MOT_JIT_MAX_CONST; + ctx->m_selfManaged = (bool*)MOT::MemSessionAlloc(allocSize); + if (ctx->m_selfManaged == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for Boolean array during code-generation context", + allocSize); + MOT::MemSessionFree(ctx->m_constValues); + return false; + } + + // we need an execution state for preparing a plan (see pl_exec.cpp) + allocSize = sizeof(PLpgSQL_execstate); + PLpgSQL_execstate* estate = (PLpgSQL_execstate*)MOT::MemSessionAlloc(allocSize); + if (estate == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for parser execution state", + (unsigned)allocSize); + MOT::MemSessionFree(ctx->m_selfManaged); + MOT::MemSessionFree(ctx->m_constValues); + return false; + } + + // setup function execution state (used during plan preparation) + plpgsql_estate_setup(estate, function, returnSetInfo); + function->cur_estate = estate; + ctx->_compiled_function = function; + + // reserve space for all parameters and locals + ctx->m_datums.resize(function->ndatums); + ctx->m_datumNulls.resize(function->ndatums); + + ctx->m_usingMagicVars = true; + ctx->m_exceptionBlockCount = 0; + ctx->m_processedBlock = nullptr; + + return true; +} + +/** @brief Destroys a compilation context. */ +static void DestroyLlvmFunctionCodeGenContext(JitLlvmFunctionCodeGenContext* ctx) +{ + if (ctx) { + // cleanup execution state + if (ctx->_compiled_function && ctx->_compiled_function->cur_estate) { + plpgsql_destroy_econtext(ctx->_compiled_function->cur_estate); + exec_eval_cleanup(ctx->_compiled_function->cur_estate); + ctx->_compiled_function->cur_estate = nullptr; + } + if (ctx->m_constValues != nullptr) { + for (uint32_t i = 0; i < ctx->m_constCount; ++i) { + if (ctx->m_selfManaged[i]) { + MOT::MemSessionFree(DatumGetPointer(ctx->m_constValues[i].constvalue)); + } + } + MOT::MemSessionFree(ctx->m_constValues); + } + if (ctx->m_selfManaged != nullptr) { + MOT::MemSessionFree(ctx->m_selfManaged); + } + if (ctx->m_resultTupDesc != nullptr) { + FreeTupleDesc(ctx->m_resultTupDesc); + ctx->m_resultTupDesc = nullptr; + } + if (ctx->m_rowTupDesc != nullptr) { + FreeTupleDesc(ctx->m_rowTupDesc); + ctx->m_rowTupDesc = nullptr; + } + } + llvm_util::DestroyLlvmCodeGenContext(ctx); +} + +static bool ValueEquals(Datum lhs, Datum rhs, int type) +{ + switch (type) { + case BOOLOID: + return DatumGetBool(DirectFunctionCall2(booleq, lhs, rhs)); + + case CHAROID: + return DatumGetBool(DirectFunctionCall2(chareq, lhs, rhs)); + + case INT1OID: + return DatumGetBool(DirectFunctionCall2(int1eq, lhs, rhs)); + + case INT2OID: + return DatumGetBool(DirectFunctionCall2(int2eq, lhs, rhs)); + + case INT4OID: + return DatumGetBool(DirectFunctionCall2(int4eq, lhs, rhs)); + + case INT8OID: + return DatumGetBool(DirectFunctionCall2(int8eq, lhs, rhs)); + + case TIMESTAMPOID: + return DatumGetBool(DirectFunctionCall2(timestamp_eq, lhs, rhs)); + + case DATEOID: + return DatumGetBool(DirectFunctionCall2(date_eq, lhs, rhs)); + + case FLOAT4OID: + return DatumGetBool(DirectFunctionCall2(float4eq, lhs, rhs)); + + case FLOAT8OID: + return DatumGetBool(DirectFunctionCall2(float8eq, lhs, rhs)); + + case VARCHAROID: + case BPCHAROID: + return DatumGetBool(DirectFunctionCall2(bpchareq, lhs, rhs)); + + case TEXTOID: + return DatumGetBool(DirectFunctionCall2(texteq, lhs, rhs)); + + case BYTEAOID: + return DatumGetBool(DirectFunctionCall2(byteaeq, lhs, rhs)); + + case TIMESTAMPTZOID: { + Datum lhsTimestamp = DirectFunctionCall1(timestamptz_timestamp, lhs); + Datum rhsTimestamp = DirectFunctionCall1(timestamptz_timestamp, rhs); + return DatumGetBool(DirectFunctionCall2(timestamp_eq, lhsTimestamp, rhsTimestamp)); + } + + case INTERVALOID: + return DatumGetBool(DirectFunctionCall2(interval_eq, lhs, rhs)); + + case TINTERVALOID: + return DatumGetBool(DirectFunctionCall2(tintervaleq, lhs, rhs)); + + case NUMERICOID: + return DatumGetBool(DirectFunctionCall2(numeric_eq, lhs, rhs)); + + default: + return false; + } +} + +static int FindConstValue(Const* constValues, uint32_t count, Datum value, Oid type, bool isNull) +{ + for (uint32_t i = 0; i < count; ++i) { + if (constValues[i].consttype == type) { + // if both are null then we have a match + if (constValues[i].constisnull && isNull) { + return (int)i; + } + // if only one is null then we definitely don't have a match + if (constValues[i].constisnull || isNull) { + continue; + } + if (ValueEquals(constValues[i].constvalue, value, type)) { + return (int)i; + } + } + } + return -1; +} + +static int AllocateConstId(JitLlvmFunctionCodeGenContext* ctx, Oid type, Datum value, bool isNull, + bool selfManaged = false, bool* pooled = nullptr) +{ + int res = FindConstValue(ctx->m_constValues, ctx->m_constCount, value, type, isNull); + if (res >= 0) { + if (pooled != nullptr) { + *pooled = true; + } + return res; + } + + if (ctx->m_constCount == MOT_JIT_MAX_CONST) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, + "JIT Compile", + "Cannot allocate constant identifier, reached limit of %u", + ctx->m_constCount); + return -1; + } + + res = ctx->m_constCount++; + ctx->m_constValues[res].consttype = type; + ctx->m_constValues[res].constisnull = isNull; + if (isNull || IsPrimitiveType(type) || selfManaged) { + ctx->m_constValues[res].constvalue = value; + ctx->m_selfManaged[res] = selfManaged; + } else { + // attention: we must clone a non-primitive, not managed, not null datum, since the plan from which the datum + // stems can be deleted when processing a simple expression + if (!CloneDatum(value, type, &ctx->m_constValues[res].constvalue, JIT_CONTEXT_LOCAL)) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "JIT Compile", "Failed to clone datum"); + return -1; + } + ctx->m_selfManaged[res] = true; + } + + MOT_LOG_TRACE("Allocated constant id: %d", res); + DEBUG_PRINT_DATUM("Allocated constant", type, value, isNull); + return res; +} + +inline llvm::Type* GetOidType(JitLlvmFunctionCodeGenContext* ctx, Oid resultType) +{ + // all types are datum + if (IsTypeSupported(resultType)) { + return ctx->DATUM_T; + } else if (resultType == UNKNOWNOID) { + MOT_LOG_TRACE("Allowing special type UNKNOWNOID"); + return ctx->DATUM_T; + } else { + MOT_LOG_TRACE("Cannot get LLVM type by OID %d", (int)resultType); + return nullptr; + } +} + +static llvm::Value* DefineVarLocal(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_var* var) +{ + // we expect scalar variable type to be predefined + llvm::Value* result = nullptr; + MOT_LOG_TRACE("Defining local scalar variable %s with type %d", var->refname, var->datatype->typoid); + llvm::Type* type = GetOidType(ctx, var->datatype->typoid); + if (type == nullptr) { + MOT_LOG_TRACE("Cannot define local variable for scalar variable %s: type not found", var->refname); + } else { + // all local variables are defined as DATUM type + // ATTENTION: sometimes locals have identical name, so we allow duplicate name + result = ctx->m_builder->CreateAlloca(type, 0, nullptr, var->refname); + } + + return result; +} + +static bool DefineBlockLocalVar(JitLlvmFunctionCodeGenContext* ctx, int varIndex) +{ + // this function is called once for defining all function local and magic variables (but not arguments) + bool result = false; + llvm::Value* localVar = nullptr; + + MOT_LOG_TRACE("Defining block local variable by varno %d", varIndex); + MOT_ASSERT(varIndex < ctx->_compiled_function->ndatums); + PLpgSQL_datum* datum = ctx->_compiled_function->datums[varIndex]; + + switch (datum->dtype) { + case PLPGSQL_DTYPE_VAR: + MOT_LOG_TRACE("Defining scalar block local variable %s", ((PLpgSQL_var*)datum)->refname); + localVar = DefineVarLocal(ctx, (PLpgSQL_var*)datum); + break; + + case PLPGSQL_DTYPE_ROW: + MOT_LOG_TRACE("Defining row block local variable"); + // this is really a non-existing variable, just a vector of local var reference by id used in INTO clause + // it should point to the result slot of the last executed query + result = true; + break; + + case PLPGSQL_DTYPE_RECORD: + // record type variable is not supported + MOT_LOG_TRACE("Unsupported record type variable"); + result = false; + break; + + case PLPGSQL_DTYPE_REC: + MOT_LOG_TRACE("Defining rec/record block local variable"); + // this is really a non-existing variable, just a map of local var reference by name used by RECFIELD vars + // it should point to the result slot of the last executed query + result = true; + break; + + case PLPGSQL_DTYPE_RECFIELD: + MOT_LOG_TRACE("Defining rec-field block local variable"); + // this is really a non-existing variable, just a reference to a record field + result = true; + break; + + case PLPGSQL_DTYPE_ARRAYELEM: + MOT_LOG_TRACE("Defining array-element block local variable"); + // this is really a non-existing variable, just a reference to an array element + result = true; + break; + + case PLPGSQL_DTYPE_EXPR: + MOT_LOG_TRACE("Defining expr block local variable"); + break; + + default: + MOT_LOG_TRACE("Invalid parameter d-type: %d", datum->dtype); + break; + } + + // save local variable in datum map of compile-context and set isnull to true, unless var has other indication + if (localVar != nullptr) { + MOT_ASSERT(ctx->m_datums[varIndex] == nullptr); + ctx->m_datums[varIndex] = localVar; + + // uninitialized local variables are set to null + MOT_ASSERT(ctx->m_datumNulls[varIndex] == nullptr); + std::string varName = "isnull"; + bool initIsNull = true; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* var = (PLpgSQL_var*)datum; + varName = std::string(var->refname) + "_isnull"; + initIsNull = var->isnull; + } + llvm::Value* isNullValue = ctx->m_builder->CreateAlloca(ctx->BOOL_T, 0, nullptr, varName.c_str()); + if (isNullValue) { + ctx->m_builder->CreateStore(JIT_CONST_BOOL(initIsNull), isNullValue, true); + ctx->m_datumNulls[varIndex] = isNullValue; + result = true; + } else { + MOT_LOG_TRACE("Failed to generate is_null value for local variable"); + } + } + + return result; +} + +static bool InitBlockLocalVar(JitLlvmFunctionCodeGenContext* ctx, int varIndex) +{ + bool result = false; + + MOT_LOG_TRACE("Initializing block local variable by var-no %d", varIndex); + MOT_ASSERT(varIndex < ctx->_compiled_function->ndatums); + PLpgSQL_datum* datum = ctx->_compiled_function->datums[varIndex]; + + switch (datum->dtype) { + case PLPGSQL_DTYPE_VAR: + MOT_LOG_DEBUG("Initializing scalar block local variable %s", ((PLpgSQL_var*)datum)->refname); + if (((PLpgSQL_var*)datum)->default_val != nullptr) { + result = AssignValue(ctx, varIndex, ((PLpgSQL_var*)datum)->default_val); + } else { + result = true; // no initialization needed + } + break; + + case PLPGSQL_DTYPE_ROW: + MOT_LOG_DEBUG("Initializing row block local variable"); + // this is really a non-existing variable, just a vector of local var reference by id used in INTO clause + // it requires no special initialization + result = true; + break; + + case PLPGSQL_DTYPE_REC: + MOT_LOG_DEBUG("Initializing rec block local variable"); + // this is really a non-existing variable, just a map of local var reference by name used by RECFIELD var + // it requires no special initialization + result = true; + break; + + case PLPGSQL_DTYPE_RECORD: + // this is Oracle-style record, we don't support it + MOT_LOG_TRACE("Unsupported record type variable"); + break; + + case PLPGSQL_DTYPE_RECFIELD: + MOT_LOG_DEBUG("Initializing rec-field block local variable"); + // this is really a non-existing variable, just a reference to a record field + result = true; + break; + + case PLPGSQL_DTYPE_ARRAYELEM: + MOT_LOG_DEBUG("Initializing array-element block local variable"); + // this is really a non-existing variable, just a reference to an array element + result = true; + break; + + case PLPGSQL_DTYPE_EXPR: + MOT_LOG_DEBUG("Initializing expr block local variable"); + break; + + default: + MOT_LOG_TRACE("Invalid parameter d-type: %d", datum->dtype); + break; + } + + return result; +} + +inline void AddSetExprIsNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* isNull) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_setExprIsNullFunc, isNull, nullptr); +} + +inline llvm::Value* AddGetExprIsNull(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getExprIsNullFunc, nullptr); +} + +inline void AddSetExprCollation(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* collation) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_setExprCollationFunc, collation, nullptr); +} + +inline llvm::Value* AddGetExprCollation(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getExprCollationFunc, nullptr); +} + +inline void AddExecClearTuple(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::Value* slot = ctx->m_builder->CreateLoad(ctx->m_slot, true); + llvm_util::AddFunctionCall(ctx, ctx->m_execClearTupleFunc, slot, nullptr); +} + +inline void AddExecStoreVirtualTuple(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::Value* slot = ctx->m_builder->CreateLoad(ctx->m_slot, true); + llvm_util::AddFunctionCall(ctx, ctx->m_execStoreVirtualTupleFunc, slot, nullptr); +} + +inline llvm::Value* AddGetParam(JitLlvmFunctionCodeGenContext* ctx, int paramId) +{ + llvm::Value* paramsValue = ctx->m_builder->CreateLoad(ctx->m_params, true); + llvm::ConstantInt* paramIdValue = llvm::ConstantInt::get(ctx->INT32_T, paramId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_getParamAtRefFunc, paramsValue, paramIdValue, nullptr); +} + +inline llvm::Value* AddIsParamNull(JitLlvmFunctionCodeGenContext* ctx, int paramId) +{ + llvm::Value* paramsValue = ctx->m_builder->CreateLoad(ctx->m_params, true); + llvm::ConstantInt* paramIdValue = llvm::ConstantInt::get(ctx->INT32_T, paramId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_isParamNullRefFunc, paramsValue, paramIdValue, nullptr); +} + +inline llvm::Value* AddIsCompositeResult(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::Value* slot = ctx->m_builder->CreateLoad(ctx->m_slot, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_isCompositeResultFunc, slot, nullptr); +} + +inline llvm::Value* AddCreateResultDatums(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_createResultDatumsFunc, nullptr); +} + +inline llvm::Value* AddCreateResultNulls(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_createResultNullsFunc, nullptr); +} + +inline void AddSetResultValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* datums, llvm::Value* nulls, int index, + llvm::Value* datum, llvm::Value* isNull) +{ + (void)llvm_util::AddFunctionCall( + ctx, ctx->m_setResultValueFunc, datums, nulls, JIT_CONST_INT32(index), datum, isNull, nullptr); +} + +inline llvm::Value* AddCreateResultHeapTuple( + JitLlvmFunctionCodeGenContext* ctx, llvm::Value* datums, llvm::Value* nulls) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_createResultHeapTupleFunc, datums, nulls, nullptr); +} + +inline void AddSetSlotValue(JitLlvmFunctionCodeGenContext* ctx, int tupleColId, llvm::Value* value, llvm::Value* isNull) +{ + llvm::Value* slot = ctx->m_builder->CreateLoad(ctx->m_slot, true); + llvm::ConstantInt* tupleColIdValue = llvm::ConstantInt::get(ctx->INT32_T, tupleColId, true); + llvm_util::AddFunctionCall(ctx, ctx->m_setSlotValueFunc, slot, tupleColIdValue, value, isNull, nullptr); +} + +inline void AddSetSPSubQueryParamValue(JitLlvmFunctionCodeGenContext* ctx, int subQueryId, int paramId, int paramType, + llvm::Value* paramValue, llvm::Value* isNull) +{ + llvm_util::AddFunctionCall(ctx, + ctx->m_setSPSubQueryParamValueFunc, + JIT_CONST_INT32(subQueryId), + JIT_CONST_INT32(paramId), + JIT_CONST_INT32(paramType), + paramValue, + isNull, + nullptr); +} + +inline llvm::Value* AddExecuteSubQuery(JitLlvmFunctionCodeGenContext* ctx, int subQueryId, int tcount) +{ + llvm::ConstantInt* subQueryIdValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryId, true); + llvm::ConstantInt* tcountValue = llvm::ConstantInt::get(ctx->INT32_T, tcount, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_executeSubQueryFunc, subQueryIdValue, tcountValue, nullptr); +} + +inline llvm::Value* AddReleaseNonJitSubQueryResources(JitLlvmFunctionCodeGenContext* ctx, int subQueryId) +{ + return llvm_util::AddFunctionCall( + ctx, ctx->m_releaseNonJitSubQueryResourcesFunc, JIT_CONST_INT32(subQueryId), nullptr); +} + +inline llvm::Value* AddGetTuplesProcessed(JitLlvmFunctionCodeGenContext* ctx, int subQueryId) +{ + llvm::ConstantInt* subQueryIdValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_getTuplesProcessedFunc, subQueryIdValue, nullptr); +} + +inline llvm::Value* AddGetSubQuerySlotValue(JitLlvmFunctionCodeGenContext* ctx, int subQueryId, int tupleColId) +{ + llvm::ConstantInt* subQueryIdValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryId, true); + llvm::ConstantInt* tupleColIdValue = llvm::ConstantInt::get(ctx->INT32_T, tupleColId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_getSubQuerySlotValueFunc, subQueryIdValue, tupleColIdValue, nullptr); +} + +inline llvm::Value* AddGetSubQuerySlotIsNull(JitLlvmFunctionCodeGenContext* ctx, int subQueryId, int tupleColId) +{ + llvm::ConstantInt* subQueryIdValue = llvm::ConstantInt::get(ctx->INT32_T, subQueryId, true); + llvm::ConstantInt* tupleColIdValue = llvm::ConstantInt::get(ctx->INT32_T, tupleColId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_getSubQuerySlotIsNullFunc, subQueryIdValue, tupleColIdValue, nullptr); +} + +inline llvm::Value* AddGetSubQueryResultHeapTuple(JitLlvmFunctionCodeGenContext* ctx, int subQueryId) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getSubQueryResultHeapTupleFunc, JIT_CONST_INT32(subQueryId), nullptr); +} + +inline llvm::Value* AddGetHeapTupleValue( + JitLlvmFunctionCodeGenContext* ctx, llvm::Value* heapTuple, int subQueryId, int columnId, llvm::Value* isNullRef) +{ + llvm::ConstantInt* subQueryIdValue = JIT_CONST_INT32(subQueryId); + llvm::ConstantInt* columnIdValue = JIT_CONST_INT32(columnId); + return llvm_util::AddFunctionCall( + ctx, ctx->m_getHeapTupleValueFunc, heapTuple, subQueryIdValue, columnIdValue, isNullRef, nullptr); +} + +inline llvm::Value* AddGetSpiResult(JitLlvmFunctionCodeGenContext* ctx, int subQueryId) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getSpiResultFunc, JIT_CONST_INT32(subQueryId), nullptr); +} + +inline void AddSetTpProcessed(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* value) +{ + llvm::Value* tp = ctx->m_builder->CreateLoad(ctx->m_tuplesProcessed, true); + llvm_util::AddFunctionCall(ctx, ctx->m_setTpProcessedFunc, tp, value, nullptr); +} + +inline void AddSetScanEnded(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* result) +{ + llvm::Value* scanEnded = ctx->m_builder->CreateLoad(ctx->m_scanEnded, true); + llvm_util::AddFunctionCall(ctx, ctx->m_setScanEndedFunc, scanEnded, result, nullptr); +} + +inline llvm::Value* AddGetCurrentSubTransactionId(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getCurrentSubTransactionIdFunc, nullptr); +} + +inline void AddBeginBlockWithExceptions(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_beginBlockWithExceptionsFunc, nullptr); +} + +inline void AddEndBlockWithExceptions(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_endBlockWithExceptionsFunc, nullptr); +} + +inline void AddEndAllBlocksWithExceptions(JitLlvmFunctionCodeGenContext* ctx) +{ + for (int i = 0; i < ctx->m_exceptionBlockCount; ++i) { + AddEndBlockWithExceptions(ctx); + CheckThrownException(ctx); + } +} + +inline void AddCleanupBlockAfterException(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_cleanupBlockAfterExceptionFunc, nullptr); +} + +inline llvm::Value* AddGetExceptionOrigin(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getExceptionOriginFunc, nullptr); +} + +inline void AddSetExceptionOrigin(JitLlvmFunctionCodeGenContext* ctx, int exceptionOrigin) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_setExceptionOriginFunc, JIT_CONST_INT32(exceptionOrigin), nullptr); +} + +inline void AddCleanupBeforeReturn(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* untilSubXid) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_cleanupBeforeReturnFunc, untilSubXid, nullptr); +} + +inline llvm::Value* AddConvertViaString(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* value, llvm::Value* resultType, + llvm::Value* targetType, llvm::Value* typeMod) +{ + return llvm_util::AddFunctionCall( + ctx, ctx->m_convertViaStringFunc, value, resultType, targetType, typeMod, nullptr); +} + +inline llvm::Value* AddCastValue(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* value, Oid sourceType, Oid targetType, + int typeMod, CoercionPathType coercePath, Oid funcId, CoercionPathType coercePath2, Oid funcId2, int nargs, + int typeByVal) +{ + return llvm_util::AddFunctionCall(ctx, + ctx->m_castValueFunc, + value, + JIT_CONST_INT32(sourceType), + JIT_CONST_INT32(targetType), + JIT_CONST_INT32(typeMod), + JIT_CONST_INT32(coercePath), + JIT_CONST_INT32(funcId), + JIT_CONST_INT32(coercePath2), + JIT_CONST_INT32(funcId2), + JIT_CONST_INT32(nargs), + JIT_CONST_INT32(typeByVal), + nullptr); +} + +inline void AddSaveErrorInfo( + JitLlvmFunctionCodeGenContext* ctx, llvm::Value* errorMessage, llvm::Value* sqlState, llvm::Value* sqlStateString) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_saveErrorInfoFunc, errorMessage, sqlState, sqlStateString, nullptr); +} + +inline llvm::Value* AddGetErrorMessage(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getErrorMessageFunc, nullptr); +} + +inline llvm::Value* AddGetSqlState(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getSqlStateFunc, nullptr); +} + +inline llvm::Value* AddGetSqlStateString(JitLlvmFunctionCodeGenContext* ctx) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getSqlStateStringFunc, nullptr); +} + +inline llvm::Value* AddGetDatumIsNotNull(JitLlvmFunctionCodeGenContext* ctx, llvm::Value* isNull) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_getDatumIsNotNullFunc, isNull, nullptr); +} + +inline llvm::Value* AddGetConstAt(JitLlvmFunctionCodeGenContext* ctx, int constId) +{ + llvm::ConstantInt* constIdValue = llvm::ConstantInt::get(ctx->INT32_T, constId, true); + return llvm_util::AddFunctionCall(ctx, ctx->m_getConstAtFunc, constIdValue, nullptr); +} + +inline void AddAbortFunction(JitLlvmFunctionCodeGenContext* ctx, int faultCode) +{ + llvm::ConstantInt* faultCodeValue = llvm::ConstantInt::get(ctx->INT32_T, faultCode, true); + llvm_util::AddFunctionCall(ctx, ctx->m_abortFunctionFunc, faultCodeValue, nullptr); +} + +inline void AddLlvmClearExceptionStack(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_llvmClearExceptionStackFunc, nullptr); +} + +inline void AddEmitProfileData( + JitLlvmFunctionCodeGenContext* ctx, uint32_t functionId, uint32_t regionId, bool startRegion) +{ + llvm::ConstantInt* functionIdValue = llvm::ConstantInt::get(ctx->INT32_T, functionId, true); + llvm::ConstantInt* regionIdValue = llvm::ConstantInt::get(ctx->INT32_T, regionId, true); + llvm::ConstantInt* startRegionValue = llvm::ConstantInt::get(ctx->INT32_T, startRegion ? 1 : 0, true); + llvm_util::AddFunctionCall( + ctx, ctx->m_emitProfileDataFunc, functionIdValue, regionIdValue, startRegionValue, nullptr); +} + +inline void InjectProfileData(JitLlvmFunctionCodeGenContext* ctx, const char* regionName, bool beginRegion) +{ + if (!MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + return; + } + + JitProfiler* jitProfiler = JitProfiler::GetInstance(); + const char* qualifiedName = GetActiveNamespace(); + uint32_t profileFunctionId = jitProfiler->GetProfileFunctionId(qualifiedName, ctx->m_plan->_function_id); + uint32_t profileRegionId = jitProfiler->GetProfileRegionId(qualifiedName, regionName); + AddEmitProfileData(ctx, profileFunctionId, profileRegionId, beginRegion); +} + +static bool CreateJittedFunction( + JitLlvmFunctionCodeGenContext* ctx, const char* functionName, const char* functionSource, bool isStrict) +{ + // although the function has a native signature as in the stored procedure text, we actually use + // a different signature, to accommodate for runtime needs: + // 1. Return value is integer (as in query jitted function) to indicate execution status + // 2. result datum or tuple (and its is-null property) is reported via the slot parameter + // 3. Return value is actually a tuple slot (good also for "returns set of") + // 4. In addition, the parameter list also contains datum+isnull pairs + // 5. Function invocation is a regular query, so for quick execution we need both jitted query and jitted function + // to have the same signature. + // 6. In fact, parameters are local variable pointers to datum objects in the ParamListInfo parameter + + llvm::Value* llvmargs[MOT_JIT_FUNC_ARG_COUNT]; + GsCodeGen::FnPrototype fn_prototype(ctx->m_codeGen, functionName, ctx->INT32_T); + + fn_prototype.addArgument(GsCodeGen::NamedVariable("params", ctx->ParamListInfoDataType->getPointerTo())); + fn_prototype.addArgument(GsCodeGen::NamedVariable("slot", ctx->TupleTableSlotType->getPointerTo())); + fn_prototype.addArgument(GsCodeGen::NamedVariable("tp_processed", ctx->INT64_T->getPointerTo())); + fn_prototype.addArgument(GsCodeGen::NamedVariable("scan_ended", ctx->INT32_T->getPointerTo())); + + ctx->m_jittedFunction = fn_prototype.generatePrototype(ctx->m_builder, &llvmargs[0]); + + // get the arguments + int arg_index = 0; + llvm::Value* paramStore = + ctx->m_builder->CreateAlloca(ctx->ParamListInfoDataType->getPointerTo(), 0, nullptr, "param_s"); + llvm::Value* slotStore = + ctx->m_builder->CreateAlloca(ctx->TupleTableSlotType->getPointerTo(), 0, nullptr, "slot_s"); + llvm::Value* tpStore = ctx->m_builder->CreateAlloca(ctx->INT64_T->getPointerTo(), 0, nullptr, "tp_s"); + llvm::Value* scanStore = ctx->m_builder->CreateAlloca(ctx->INT32_T->getPointerTo(), 0, nullptr, "scan_s"); + ctx->m_builder->CreateStore(llvmargs[arg_index++], paramStore, true); + ctx->m_builder->CreateStore(llvmargs[arg_index++], slotStore, true); + ctx->m_builder->CreateStore(llvmargs[arg_index++], tpStore, true); + ctx->m_builder->CreateStore(llvmargs[arg_index++], scanStore, true); + ctx->m_params = paramStore; + ctx->m_slot = slotStore; + ctx->m_tuplesProcessed = tpStore; + ctx->m_scanEnded = scanStore; + + JIT_DEBUG_LOG("Starting execution of jitted function"); + + // prepare datum values for parameters: + // 1. in this translation process, we rely on the fact that function parameters varnos are always first, + // starting from zero + // 2. pay attention that all parameter types are DATUM_T + // 3. for the case of default parameters, we make sure that invoke code generation passes all missing parameters + // so that the function being invoked always gets all required parameters. + // 4. in this process we collect all default values into an array to be used by invoke code generation + int argCount = ctx->_compiled_function->fn_nargs; + MOT_LOG_TRACE("Initializing %d function arguments", argCount); + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_TOTAL, true); + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_DEF_VARS, true); + for (int i = 0; i < argCount; ++i) { + int argVarNo = ctx->_compiled_function->fn_argvarnos[i]; + std::string pName = "p_" + std::to_string(argVarNo); + std::string pNullName = "pNull_" + std::to_string(argVarNo); + MOT_ASSERT(ctx->m_datums[argVarNo] == nullptr); + MOT_ASSERT(ctx->m_datumNulls[argVarNo] == nullptr); + MOT_LOG_TRACE("Defining parameter %d (global index %d)", i, argVarNo); + llvm::Value* pStore = ctx->m_builder->CreateAlloca(ctx->DATUM_T->getPointerTo(), 0, nullptr, pName.c_str()); + llvm::Value* pNullStore = + ctx->m_builder->CreateAlloca(ctx->BOOL_T->getPointerTo(), 0, nullptr, pNullName.c_str()); + llvm::Value* param = AddGetParam(ctx, i); + + if (param == nullptr) { + MOT_LOG_TRACE("Failed to define parameter %d (global index %d)", i, argVarNo); + return false; + } + + ctx->m_builder->CreateStore(param, pStore, true); + llvm::Value* isParamNull = AddIsParamNull(ctx, i); + + if (isParamNull == nullptr) { + MOT_LOG_TRACE("Failed to define parameter %d (global index %d)", i, argVarNo); + return false; + } + + ctx->m_builder->CreateStore(isParamNull, pNullStore, true); + // attention: returned values from AddGetParam() is l-value (pointer) + ctx->m_datums[argVarNo] = ctx->m_builder->CreateLoad(pStore, true); + ctx->m_datumNulls[argVarNo] = ctx->m_builder->CreateLoad(pNullStore, true); + } + + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_DEF_VARS, false); + + OpenCompileFrame(ctx); + + // generate code for strict functions - check no null arguments + if (isStrict) { + for (int i = 0; i < argCount; ++i) { + int argVarNo = ctx->_compiled_function->fn_argvarnos[i]; + JIT_IF_BEGIN(has_null_param); + llvm::Value* isNull = ctx->m_builder->CreateLoad(ctx->m_datumNulls[argVarNo], true); + JIT_IF_EVAL(isNull); + { + // this is OK - strict execution forces returning nullptr tuple + AddExecClearTuple(ctx); + JIT_RETURN(JIT_CONST_INT32(0)); + } + JIT_IF_END(); + } + } + + return true; +} + +static MotJitContext* FinalizeCodegen( + JitLlvmFunctionCodeGenContext* ctx, JitFunctionPlan* plan, const char* functionName, JitCodegenStats& codegenStats) +{ + CloseCompileFrame(ctx); + // do GsCodeGen stuff to wrap up + uint64_t startTime = GetSysClock(); + if (!ctx->m_codeGen->verifyFunction(ctx->m_jittedFunction)) { + MOT_LOG_ERROR("Failed to generate jitted code for stored procedure: Failed to verify jit llvm function"); +#ifdef LLVM_ENABLE_DUMP + ctx->m_jittedFunction->dump(); +#else + ctx->m_jittedFunction->print(llvm::errs(), nullptr, false, true); +#endif + (void)fflush(stderr); + // print informative error + llvm::Function::iterator itr = ctx->m_jittedFunction->begin(); + for (; itr != ctx->m_jittedFunction->end(); ++itr) { + if (itr->empty()) { + MOT_LOG_ERROR("Block %s is empty", itr->getName().data()); + break; + } + if (!itr->back().isTerminator()) { + MOT_LOG_ERROR("Block %s does not end with terminator", itr->getName().data()) + break; + } + } + + return nullptr; + } + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_verifyTime = timeMicros; + MOT_LOG_TRACE("Function '%s' verification time: %" PRIu64 " micros", functionName, timeMicros); + + startTime = GetSysClock(); + ctx->m_codeGen->FinalizeFunction(ctx->m_jittedFunction); + endTime = GetSysClock(); + timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_finalizeTime = timeMicros; + MOT_LOG_TRACE("Function '%s' finalization time: %" PRIu64 " micros", functionName, timeMicros); + + // print if we are either configured to print or we are in debug mode +#ifdef MOT_JIT_DEBUG + bool dumpFunction = true; +#else + bool dumpFunction = false; +#endif + + if (dumpFunction || IsMotCodegenPrintEnabled()) { +#ifdef LLVM_ENABLE_DUMP + ctx->m_jittedFunction->dump(); +#else + ctx->m_jittedFunction->print(llvm::errs()); +#endif + (void)fflush(stderr); + } + + // that's it, we are ready + JitFunctionContext* jitContext = + (JitFunctionContext*)AllocJitContext(JIT_CONTEXT_GLOBAL, JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + if (jitContext == nullptr) { + MOT_LOG_TRACE("Failed to allocate JIT context, aborting code generation"); + return nullptr; + } + + MOT_LOG_DEBUG("Adding function to MCJit"); + ctx->m_codeGen->addFunctionToMCJit(ctx->m_jittedFunction, (void**)&jitContext->m_llvmSPFunction); + + MOT_LOG_DEBUG("Generating code..."); + startTime = GetSysClock(); + ctx->m_codeGen->enableOptimizations(true); + ctx->m_codeGen->compileCurrentModule(false); + endTime = GetSysClock(); + timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_compileTime = timeMicros; + MOT_LOG_TRACE("Function '%s' compilation time: %" PRIu64 " micros", functionName, timeMicros); + + // prepare global constant array + if (ctx->m_constCount > 0) { + if (!PrepareDatumArray(ctx->m_constValues, ctx->m_constCount, &jitContext->m_constDatums)) { + MOT_LOG_ERROR("Failed to generate jitted code for function: Failed to prepare constant datum array"); + DestroyJitContext(jitContext); + return nullptr; + } + } + + // setup execution details + jitContext->m_SPArgCount = (uint64_t)ctx->_compiled_function->fn_nargs; + jitContext->m_codeGen = ctx->m_codeGen; // make sure module is not destroyed + ctx->m_codeGen = nullptr; // prevent destruction + jitContext->m_validState = JIT_CONTEXT_VALID; + jitContext->m_commandType = JIT_COMMAND_FUNCTION; + jitContext->m_functionOid = plan->_function_id; + jitContext->m_functionTxnId = plan->m_function->fn_xmin; + jitContext->m_paramCount = plan->m_paramCount; + if (jitContext->m_paramCount > 0) { + size_t allocSize = sizeof(Oid) * jitContext->m_paramCount; + jitContext->m_paramTypes = (Oid*)MOT::MemGlobalAlloc(allocSize); + if (jitContext->m_paramTypes == nullptr) { + MOT_LOG_TRACE("Failed to allocate %u bytes for %u parameter type items in JIT function context", + (unsigned)allocSize, + jitContext->m_paramCount); + DestroyJitContext(jitContext); + return nullptr; + } + errno_t erc = memcpy_s(jitContext->m_paramTypes, allocSize, plan->m_paramTypes, allocSize); + securec_check(erc, "\0", "\0"); + } else { + jitContext->m_paramTypes = nullptr; + } + jitContext->m_SPSubQueryCount = plan->_query_count; + if (jitContext->m_SPSubQueryCount > 0) { + size_t allocSize = sizeof(JitCallSite) * jitContext->m_SPSubQueryCount; + jitContext->m_SPSubQueryList = (JitCallSite*)MOT::MemGlobalAlloc(allocSize); + if (jitContext->m_SPSubQueryList == nullptr) { + MOT_LOG_TRACE("Failed to allocate %u bytes for %u sub-query data items in JIT function context", + (unsigned)allocSize, + (unsigned)jitContext->m_SPSubQueryCount); + DestroyJitContext(jitContext); + return nullptr; + } + errno_t erc = memset_s(jitContext->m_SPSubQueryList, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // initialize the sub-query data, compile all sub-queries + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSitePlan* callSitePlan = &plan->m_callSitePlanList[i]; + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (!ProcessCallSitePlan(callSitePlan, callSite, ctx->_compiled_function)) { + MOT_LOG_TRACE("Failed to process call site plan %d: %s", i, callSitePlan->m_queryString); + DestroyJitContext(jitContext); + return nullptr; + } + if (callSite->m_queryContext != nullptr) { + callSite->m_queryContext->m_parentContext = jitContext; + } + } + } + jitContext->m_compositeResult = (ctx->m_typeFuncClass == TYPEFUNC_COMPOSITE) ? 1 : 0; + jitContext->m_resultTupDesc = ctx->m_resultTupDesc; + jitContext->m_rowTupDesc = ctx->m_rowTupDesc; + ctx->m_resultTupDesc = nullptr; + ctx->m_rowTupDesc = nullptr; + + return jitContext; +} + +static bool ProcessStatement(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt* stmt) +{ + JIT_DEBUG_LOG_INT("Executing line %d", stmt->lineno); + switch (stmt->cmd_type) { + case PLPGSQL_STMT_BLOCK: + return ProcessStatementBlock(ctx, (PLpgSQL_stmt_block*)stmt); + case PLPGSQL_STMT_ASSIGN: + return ProcessStatementAssign(ctx, (PLpgSQL_stmt_assign*)stmt); + case PLPGSQL_STMT_IF: + return ProcessStatementIf(ctx, (PLpgSQL_stmt_if*)stmt); + case PLPGSQL_STMT_GOTO: + return ProcessStatementGoto(ctx, (PLpgSQL_stmt_goto*)stmt); + case PLPGSQL_STMT_CASE: + return ProcessStatementCase(ctx, (PLpgSQL_stmt_case*)stmt); + case PLPGSQL_STMT_LOOP: + return ProcessStatementLoop(ctx, (PLpgSQL_stmt_loop*)stmt); + case PLPGSQL_STMT_WHILE: + return ProcessStatementWhile(ctx, (PLpgSQL_stmt_while*)stmt); + case PLPGSQL_STMT_FORI: + return ProcessStatementForI(ctx, (PLpgSQL_stmt_fori*)stmt); + case PLPGSQL_STMT_FORS: + return ProcessStatementForS(ctx, (PLpgSQL_stmt_fors*)stmt); + case PLPGSQL_STMT_FORC: + return ProcessStatementForC(ctx, (PLpgSQL_stmt_forc*)stmt); + case PLPGSQL_STMT_FOREACH_A: + return ProcessStatementForEach(ctx, (PLpgSQL_stmt_foreach_a*)stmt); + case PLPGSQL_STMT_EXIT: + return ProcessStatementExit(ctx, (PLpgSQL_stmt_exit*)stmt); + case PLPGSQL_STMT_RETURN: + return ProcessStatementReturn(ctx, (PLpgSQL_stmt_return*)stmt); + case PLPGSQL_STMT_RETURN_NEXT: + return ProcessStatementReturnNext(ctx, (PLpgSQL_stmt_return_next*)stmt); + case PLPGSQL_STMT_RETURN_QUERY: + return ProcessStatementReturnQuery(ctx, (PLpgSQL_stmt_return_query*)stmt); + case PLPGSQL_STMT_RAISE: + return ProcessStatementRaise(ctx, (PLpgSQL_stmt_raise*)stmt); + case PLPGSQL_STMT_EXECSQL: + return ProcessStatementExecSql(ctx, (PLpgSQL_stmt_execsql*)stmt); + case PLPGSQL_STMT_DYNEXECUTE: + return ProcessStatementDynExecute(ctx, (PLpgSQL_stmt_dynexecute*)stmt); + case PLPGSQL_STMT_DYNFORS: + return ProcessStatementDynForS(ctx, (PLpgSQL_stmt_dynfors*)stmt); + case PLPGSQL_STMT_GETDIAG: + return ProcessStatementGetDiag(ctx, (PLpgSQL_stmt_getdiag*)stmt); + case PLPGSQL_STMT_OPEN: + return ProcessStatementOpen(ctx, (PLpgSQL_stmt_open*)stmt); + case PLPGSQL_STMT_FETCH: + return ProcessStatementFetch(ctx, (PLpgSQL_stmt_fetch*)stmt); + case PLPGSQL_STMT_CLOSE: + return ProcessStatementClose(ctx, (PLpgSQL_stmt_close*)stmt); + case PLPGSQL_STMT_PERFORM: + return ProcessStatementPerform(ctx, (PLpgSQL_stmt_perform*)stmt); + case PLPGSQL_STMT_NULL: + return true; + + default: + MOT_LOG_TRACE("Failed to process statement: invalid statement type %d", stmt->cmd_type); + return false; + } +} + +static bool InitBlockVars(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_block* stmt) +{ + MOT_LOG_TRACE("Initializing block local variables"); + for (int i = 0; i < stmt->n_initvars; ++i) { + if (!InitBlockLocalVar(ctx, stmt->initvarnos[i])) { + MOT_LOG_TRACE("InitBlockVars(): Failed to initialize local variable %d (global index %d) in block %s", + i, + stmt->initvarnos[i], + stmt->label); + return false; + } + } + return true; +} + +static bool ProcessStatementList(JitLlvmFunctionCodeGenContext* ctx, List* actions) +{ + bool result = true; + + int stmtIndex = 0; // for diagnostics + ListCell* lc = nullptr; + + foreach (lc, actions) { + PLpgSQL_stmt* stmt = (PLpgSQL_stmt*)lfirst(lc); + if (!ProcessStatement(ctx, stmt)) { + MOT_LOG_TRACE("processStatementList(): Failed to process statement %d", stmtIndex); + result = false; + break; + } + ++stmtIndex; + } + + return result; +} + +static bool ProcessStatementBlockBody(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_block* stmt) +{ + // each block has local variables, so we define them now + // we also make sure to use a compile-time frame + OpenCompileFrame(ctx); + + if (!InitBlockVars(ctx, stmt)) { + MOT_LOG_TRACE("ProcessStatementBlockBody(): Failed to initialize block variables"); + return false; + } + + MOT_LOG_TRACE("ProcessStatementBlockBody(): processing block body"); + if (!ProcessStatementList(ctx, stmt->body)) { + MOT_LOG_TRACE("ProcessStatementBlockBody(): Failed to process block body"); + return false; + } + + CloseCompileFrame(ctx); + return true; +} + +static bool MakeStringDatum(const char* message, Datum* target) +{ + size_t strSize = strlen(message); + size_t allocSize = VARHDRSZ + strSize + 1; + bytea* copy = (bytea*)MOT::MemSessionAlloc(allocSize); + if (copy == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "JIT Compile", "Failed to allocate %u bytes for datum string constant", (unsigned)allocSize); + return false; + } + + errno_t erc = memcpy_s(VARDATA(copy), strSize, (uint8_t*)message, strSize); + securec_check(erc, "\0", "\0"); + + VARDATA(copy)[strSize] = 0; + SET_VARSIZE(copy, allocSize); + + *target = PointerGetDatum(copy); + return true; +} + +static void FreeStringDatum(Datum value) +{ + bytea* txt = DatumGetByteaP(value); + if (txt != nullptr) { + MOT::MemSessionFree(txt); + } +} + +static llvm::Value* GetStringConst(JitLlvmFunctionCodeGenContext* ctx, const char* str) +{ + Datum value = PointerGetDatum(nullptr); + if (!MakeStringDatum(str, &value)) { + MOT_LOG_TRACE("Failed to prepare string datum for string: %s", str); + return nullptr; + } + bool pooled = false; + int constId = AllocateConstId(ctx, VARCHAROID, value, false, true, &pooled); + if (constId == -1) { + MOT_LOG_TRACE("Failed to allocate constant identifier for string: %s", str); + FreeStringDatum(value); + return nullptr; + } + MOT_LOG_TRACE("Allocated const id %d for string: %s", constId, str); + if (pooled) { + FreeStringDatum(value); + } + llvm::Value* strValue = AddGetConstAt(ctx, constId); + return strValue; +} + +static bool AddSaveErrorInfoInContext( + JitLlvmFunctionCodeGenContext* ctx, const char* errorMessage, int sqlState, char* sqlStateCode) +{ + llvm::Value* errorMessageValue = GetStringConst(ctx, errorMessage); + llvm::Value* sqlStateValue = JIT_CONST_INT32(sqlState); + llvm::Value* sqlStateStringValue = GetStringConst(ctx, sqlStateCode); + AddSaveErrorInfo(ctx, errorMessageValue, sqlStateValue, sqlStateStringValue); + return true; +} + +static bool AddAssignErrorText(JitLlvmFunctionCodeGenContext* ctx, int varNo, const char* errorMessage) +{ + llvm::Value* errorValue = GetLocalOrParamByVarNo(ctx, varNo); + llvm::Value* errorIsNull = GetIsNullLocalOrParamByVarNo(ctx, varNo); + + llvm::Value* errorMessageValue = GetStringConst(ctx, errorMessage); + ctx->m_builder->CreateStore(errorMessageValue, errorValue, true); + ctx->m_builder->CreateStore(JIT_CONST_BOOL(false), errorIsNull, true); + JIT_DEBUG_LOG_INT("Assigned error text to varno: %d", varNo); + JIT_DEBUG_LOG_VARNO(varNo); + +#ifdef MOT_JIT_DEBUG + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(errorIsNull, true); + JIT_DEBUG_LOG_INT_VAR("Error text is null var value: %d", loadedValue); + loadedValue = ctx->m_builder->CreateLoad(errorValue, true); + JIT_DEBUG_LOG_STRING_DATUM_VAR("Error text stored: ", loadedValue); +#endif + return true; +} + +static bool AddSetSqlStateAndErrorMessage(JitLlvmFunctionCodeGenContext* ctx, const char* errorMessage, int sqlState) +{ + // prepare sql state text consisting of 6 chars + char sqlStateCode[6] = {}; + SqlStateToCode(sqlState, sqlStateCode); + + PLpgSQL_stmt_block* stmt = ctx->m_processedBlock; + if ((stmt == nullptr) || (stmt->exceptions == nullptr)) { + // this is a function which does not have an exception block, but still throws an exception. It is assumed + // that there is a calling function which should catch this exception, so we save the exception information + // in the backing context of the this function + if (!AddSaveErrorInfoInContext(ctx, errorMessage, sqlState, sqlStateCode)) { + MOT_LOG_TRACE("Failed to save error text in backing context"); + return false; + } + return true; + } + JIT_DEBUG_LOG_INT("Assigning SQLERRM message into varno: %d", ctx->_compiled_function->sqlerrm_varno); + JIT_DEBUG_LOG_STRING("Assigning SQLERRM message: %s", errorMessage); + if (!AddAssignErrorText(ctx, ctx->_compiled_function->sqlerrm_varno, errorMessage)) { + MOT_LOG_TRACE("Failed to assign error text for sqlerrm"); + return false; + } + + if (!AddAssignErrorText(ctx, ctx->_compiled_function->sqlstate_varno, sqlStateCode)) { + MOT_LOG_TRACE("Failed to assign error text for sqlstate"); + return false; + } + return true; +} + +inline void UpdateSqlErrorMessageVar(JitLlvmFunctionCodeGenContext* ctx) +{ + // access directly to avoid endless recursion + llvm::Value* errorMessage = AddGetErrorMessage(ctx); + llvm::Value* errorValue = ctx->m_datums[ctx->_compiled_function->sqlerrm_varno]; + llvm::Value* errorIsNull = ctx->m_datumNulls[ctx->_compiled_function->sqlerrm_varno]; + ctx->m_builder->CreateStore(errorMessage, errorValue, true); + ctx->m_builder->CreateStore(JIT_CONST_BOOL(false), errorIsNull, true); +} + +inline void UpdateSqlStateVar(JitLlvmFunctionCodeGenContext* ctx) +{ + // access directly to avoid endless recursion + llvm::Value* sqlState = AddGetSqlStateString(ctx); + llvm::Value* sqlStateValue = ctx->m_datums[ctx->_compiled_function->sqlstate_varno]; + llvm::Value* sqlStateIsNull = ctx->m_datumNulls[ctx->_compiled_function->sqlstate_varno]; + ctx->m_builder->CreateStore(sqlState, sqlStateValue, true); + ctx->m_builder->CreateStore(JIT_CONST_BOOL(false), sqlStateIsNull, true); +} + +inline void UpdateSqlCodeVar(JitLlvmFunctionCodeGenContext* ctx) +{ + // access directly to avoid endless recursion + llvm::Value* sqlCode = nullptr; + if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) { + sqlCode = AddGetSqlStateString(ctx); + } else { + sqlCode = AddGetSqlState(ctx); + } + llvm::Value* sqlCodeValue = ctx->m_datums[ctx->_compiled_function->sqlcode_varno]; + llvm::Value* sqlCodeIsNull = ctx->m_datumNulls[ctx->_compiled_function->sqlcode_varno]; + ctx->m_builder->CreateStore(sqlCode, sqlCodeValue, true); + ctx->m_builder->CreateStore(JIT_CONST_BOOL(false), sqlCodeIsNull, true); +} + +static bool AddLoadErrorInfoFromContext(JitLlvmFunctionCodeGenContext* ctx) +{ + PLpgSQL_stmt_block* stmt = ctx->m_processedBlock; + if ((stmt != nullptr) && (stmt->exceptions != nullptr)) { + UpdateSqlErrorMessageVar(ctx); + UpdateSqlStateVar(ctx); + UpdateSqlCodeVar(ctx); + } + return true; +} + +static bool ProcessStatementBlockWithExceptions(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_block* stmt) +{ + if (!InitBlockVars(ctx, stmt)) { + MOT_LOG_TRACE("ProcessStatementBlockWithExceptions(): Failed to initialize block variables"); + return false; + } + + // execute block entry (includes starting a new sub-transaction) + AddBeginBlockWithExceptions(ctx); + CheckThrownException(ctx); + + bool result = true; + JIT_TRY_BEGIN(stmt_block_try) + { + LabelDesc labelDesc = {stmt->label, nullptr, JIT_TRY_POST_BLOCK(), false}; + ctx->m_labelStack.push_back(labelDesc); + MOT_LOG_TRACE("processStatementBlockWithExceptions(): processing block body"); + if (!ProcessStatementList(ctx, stmt->body)) { + MOT_LOG_TRACE("processStatementBlockWithExceptions(): Failed to process block body"); + result = false; + } else { + // Commit the inner transaction, return to outer xact context + // NOTE: be careful, block might have ended already with terminator + // ATTENTION: this is an open use case - we need to fix all return statements in all relevant blocks + llvm::Instruction* termInst = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (termInst == nullptr) { + // commit and execute block exit + AddEndBlockWithExceptions(ctx); + CheckThrownException(ctx); + } else { + MOT_LOG_TRACE("processStatementBlockWithExceptions(): statement list ended with terminator"); + } + + // now process block exceptions + bool catchAllIssued = false; + ListCell* lc = nullptr; + foreach (lc, stmt->exceptions->exc_list) { + PLpgSQL_exception* exceptionBlock = (PLpgSQL_exception*)lfirst(lc); + MOT_LOG_TRACE("processStatementBlockWithExceptions(): Adding catch block"); + PLpgSQL_condition* cond = exceptionBlock->conditions; + // single SQL error state denotes "WHEN OTHERS THEN", so this is a catch-all block + if (cond->sqlerrstate == 0) { + if (cond->next != nullptr) { + MOT_LOG_TRACE("processStatementBlockWithExceptions(): Invalid exception catch-all block with " + "multiple conditions"); + result = false; + break; + } + JIT_CATCH_ALL(); + catchAllIssued = true; + } else { + while (cond) { + MOT_LOG_TRACE( + "processStatementBlockWithExceptions(): Adding catch handler for SQL error state: %d", + cond->sqlerrstate); + JIT_BEGIN_CATCH_MANY(JIT_CONST_INT32(cond->sqlerrstate)); + cond = cond->next; + } + JIT_CATCH_MANY_TERM(); + } + MOT_LOG_TRACE("processStatementBlockWithExceptions(): Processing catch block statements"); + + // in every case of caught exception we first rollback current transaction + JIT_DEBUG_LOG("Executing SP catch handler statements"); + // ATTENTION: when the exception is thrown from within the jitted function we should not load the error + // info into SQLERRM, but when it was thrown from within a helper we should. This should be done before + // any cleanup, or else the original error might get lost + JIT_IF_BEGIN(load_error_info); + llvm::Value* exceptionOrigin = AddGetExceptionOrigin(ctx); + JIT_IF_EVAL_CMP(exceptionOrigin, JIT_CONST_INT32(JIT_EXCEPTION_EXTERNAL), JitICmpOp::JIT_ICMP_EQ); + { + JIT_DEBUG_LOG("Loading error info from context"); + AddLoadErrorInfoFromContext(ctx); + // reset the exception origin + AddSetExceptionOrigin(ctx, JIT_EXCEPTION_INTERNAL); + } + JIT_IF_END(); + JIT_DEBUG_LOG_VARNO(ctx->_compiled_function->sqlerrm_varno); + AddCleanupBlockAfterException(ctx); + CheckThrownException(ctx); + --ctx->m_exceptionBlockCount; // one exception block handled + + // attention: sql error message and state are already set up when JIT exception is thrown + + // and process the exception statement list + if (!ProcessStatementList(ctx, exceptionBlock->action)) { + MOT_LOG_TRACE("processStatementBlockWithExceptions(): Failed to process exception handler: Failed " + "to process exception statement list"); + result = false; + break; + } + JIT_DEBUG_LOG("Finished executing SP catch handler statements"); + JIT_END_CATCH(); + ++ctx->m_exceptionBlockCount; // return to previous state + } + + if (result && !catchAllIssued) { + // we must issue a catch-all if none was issued so that rollback will be called in any case + JIT_CATCH_ALL(); + { + JIT_DEBUG_LOG("Forced catch-all for sub-tx rollback"); + // we do not save sqlerrm, just fold back properly + AddCleanupBlockAfterException(ctx); + CheckThrownException(ctx); + --ctx->m_exceptionBlockCount; // one exception block handled + JIT_RETHROW(); + } + JIT_END_CATCH(); + } + } + } + JIT_TRY_END() + ctx->m_labelStack.pop_back(); + + return result; +} + +static bool ProcessStatementLabel(JitLlvmFunctionCodeGenContext* ctx, const char* label, const char* defaultName) +{ + bool result = true; + + // if label exists, then create a named block and add to goto map + // otherwise just create a block + if (label && (ctx->m_gotoLabels.find(label) != ctx->m_gotoLabels.end())) { + MOT_LOG_TRACE("Invalid block name %s: already exists", label); + result = false; + } else { + llvm::BasicBlock* block = + llvm::BasicBlock::Create(ctx->m_codeGen->context(), label ? label : defaultName, ctx->m_jittedFunction); + if (label) { + result = ctx->m_gotoLabels.insert(GotoLabelMap::value_type(label, block)).second; + MOT_ASSERT(result); + } + if (result) { + // terminate previous block and start a new one (so that goto would work properly) + ctx->m_builder->CreateBr(block); + ctx->m_builder->SetInsertPoint(block); + } + } + + return result; +} + +static bool ProcessStatementBlock(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_block* stmt) +{ + MOT_LOG_TRACE("Processing statement: block"); + PLpgSQL_stmt_block* enclosingBlock = ctx->m_processedBlock; + if (stmt->exceptions != nullptr) { + ctx->m_processedBlock = stmt; + } + bool result = ProcessStatementLabel(ctx, stmt->label, "unnamed_stmt_block"); + if (result) { + if (stmt->exceptions) { + if (ctx->m_exceptionBlockCount >= MOT_JIT_MAX_BLOCK_DEPTH) { + MOT_LOG_TRACE("Failed to process function: Reached maximum nesting level of blocks with exceptions"); + result = false; + } else { + ++ctx->m_exceptionBlockCount; + result = ProcessStatementBlockWithExceptions(ctx, stmt); + --ctx->m_exceptionBlockCount; + } + } else { + // create post block to provide a branch point for EXIT statement + llvm::BasicBlock* block = nullptr; + if (stmt->label != nullptr) { + block = llvm::BasicBlock::Create(ctx->m_codeGen->context(), stmt->label, ctx->m_jittedFunction); + LabelDesc labelDesc = {stmt->label, nullptr, block, false}; + ctx->m_labelStack.push_back(labelDesc); + } + + result = ProcessStatementBlockBody(ctx, stmt); + + // terminate previous block and start a new one (so that goto would work properly) + if (stmt->label != nullptr) { + ctx->m_labelStack.pop_back(); + ctx->m_builder->CreateBr(block); + ctx->m_builder->SetInsertPoint(block); + } + } + } + ctx->m_processedBlock = enclosingBlock; + + return result; +} + +static void AddValidateSubXid(JitLlvmFunctionCodeGenContext* ctx) +{ + llvm::Value* endSubXid = AddGetCurrentSubTransactionId(ctx); + JIT_IF_BEGIN(validate_sub_xid) + JIT_IF_EVAL_CMP(ctx->m_initSubXid, endSubXid, JitExec::JIT_ICMP_NE); + { + AddAbortFunction(ctx, JIT_FAULT_SUB_TX_NOT_CLOSED); + } + JIT_IF_END() +} + +static bool ProcessFunctionAction(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_function* function) +{ + // although we do not have an exception block, we must have some enclosing try-catch block, because sometimes + // we generate code for throwing exceptions + bool result = true; + ctx->m_initSubXid = AddGetCurrentSubTransactionId(ctx); + JIT_TRY_BEGIN(top_level) + { + if (!ProcessStatementBlock(ctx, function->action)) { + MOT_LOG_TRACE("Failed to process function action block"); + result = false; + } + } + if (result) { + JIT_CATCH_ALL(); + { + // cleanup and report error to user + JIT_DEBUG_LOG("Uncaught exception in top level block"); + AddLlvmClearExceptionStack(ctx); // clean up run-time exception stack + + // inject profile data before exiting + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_TOTAL, false); + + AddValidateSubXid(ctx); + + // since this might be a normal flow of events for sub-SP, we return a special return code to avoid error + // reporting in such case + JIT_RETURN(JIT_CONST_INT32(MOT::RC_JIT_SP_EXCEPTION)); + } + JIT_END_CATCH(); + } + JIT_TRY_END() + + // we want to avoid a post-try block without instructions at all, let alone terminators + AddAbortFunction(ctx, JIT_FAULT_INTERNAL_ERROR); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_ERROR)); + + return result; +} + +static llvm::Value* GetLocalOrParamByVarNo(JitLlvmFunctionCodeGenContext* ctx, int varNo) +{ + llvm::Value* result = nullptr; + if (varNo < (int)ctx->m_datums.size()) { + result = ctx->m_datums[varNo]; + if (result == nullptr) { + MOT_LOG_TRACE("Cannot retrieve parameter or local variable in index %d: entry is nullptr", varNo); + MOT_ASSERT(false); + } + } else { + MOT_LOG_TRACE("Cannot retrieve parameter or local variable: invalid index %d", varNo); + } + return result; +} + +static llvm::Value* GetIsNullLocalOrParamByVarNo(JitLlvmFunctionCodeGenContext* ctx, int varNo) +{ + llvm::Value* result = nullptr; + if (varNo < (int)ctx->m_datumNulls.size()) { + result = ctx->m_datumNulls[varNo]; + if (!result) { + MOT_LOG_TRACE("Cannot retrieve null for parameter or local variable in index %d: entry is nullptr", varNo); + MOT_ASSERT(false); + } + } else { + MOT_LOG_TRACE("Cannot retrieve null for parameter or local variable: invalid index %d", varNo); + } + return result; +} + +static bool ExecSimpleCheckNode(Node* node) +{ + if (node == nullptr) { + return true; + } + + switch (nodeTag(node)) { + case T_Const: + return true; + + case T_Param: + return true; + + case T_ArrayRef: { + ArrayRef* expr = (ArrayRef*)node; + if (!ExecSimpleCheckNode((Node*)expr->refupperindexpr)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->reflowerindexpr)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->refexpr)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->refassgnexpr)) { + return false; + } + return true; + } + + case T_FuncExpr: { + FuncExpr* expr = (FuncExpr*)node; + if (expr->funcretset) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + if (!IsFunctionSupported(expr)) { + return false; + } + return true; + } + + case T_OpExpr: { + OpExpr* expr = (OpExpr*)node; + if (expr->opretset) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_DistinctExpr: { + DistinctExpr* expr = (DistinctExpr*)node; + if (expr->opretset) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_NullIfExpr: { + NullIfExpr* expr = (NullIfExpr*)node; + if (expr->opretset) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_ScalarArrayOpExpr: { + ScalarArrayOpExpr* expr = (ScalarArrayOpExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_BoolExpr: { + BoolExpr* expr = (BoolExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_FieldSelect: + return ExecSimpleCheckNode((Node*)((FieldSelect*)node)->arg); + + case T_FieldStore: { + FieldStore* expr = (FieldStore*)node; + if (!ExecSimpleCheckNode((Node*)expr->arg)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->newvals)) { + return false; + } + return true; + } + + case T_RelabelType: + return ExecSimpleCheckNode((Node*)((RelabelType*)node)->arg); + + case T_CoerceViaIO: + return ExecSimpleCheckNode((Node*)((CoerceViaIO*)node)->arg); + + case T_ArrayCoerceExpr: + return ExecSimpleCheckNode((Node*)((ArrayCoerceExpr*)node)->arg); + + case T_ConvertRowtypeExpr: + return ExecSimpleCheckNode((Node*)((ConvertRowtypeExpr*)node)->arg); + + case T_CaseExpr: { + CaseExpr* expr = (CaseExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->arg)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->defresult)) { + return false; + } + return true; + } + + case T_CaseWhen: { + CaseWhen* when = (CaseWhen*)node; + if (!ExecSimpleCheckNode((Node*)when->expr)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)when->result)) { + return false; + } + return true; + } + + case T_CaseTestExpr: + return true; + + case T_ArrayExpr: { + ArrayExpr* expr = (ArrayExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->elements)) { + return false; + } + return true; + } + + case T_RowExpr: { + RowExpr* expr = (RowExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_RowCompareExpr: { + RowCompareExpr* expr = (RowCompareExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->largs)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->rargs)) { + return false; + } + return true; + } + + case T_CoalesceExpr: { + CoalesceExpr* expr = (CoalesceExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_MinMaxExpr: { + MinMaxExpr* expr = (MinMaxExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_XmlExpr: { + XmlExpr* expr = (XmlExpr*)node; + if (!ExecSimpleCheckNode((Node*)expr->named_args)) { + return false; + } + if (!ExecSimpleCheckNode((Node*)expr->args)) { + return false; + } + return true; + } + + case T_NullTest: + return ExecSimpleCheckNode((Node*)((NullTest*)node)->arg); + + case T_HashFilter: + return ExecSimpleCheckNode((Node*)((HashFilter*)node)->arg); + + case T_BooleanTest: + return ExecSimpleCheckNode((Node*)((BooleanTest*)node)->arg); + + case T_CoerceToDomain: + return ExecSimpleCheckNode((Node*)((CoerceToDomain*)node)->arg); + + case T_CoerceToDomainValue: + return true; + + case T_List: { + List* expr = (List*)node; + ListCell* l = nullptr; + foreach (l, expr) { + if (!ExecSimpleCheckNode((Node*)lfirst(l))) { + return false; + } + } + return true; + } + + default: + return false; + } +} + +static bool RecheckExprSimplePlan(PLpgSQL_expr* expr, CachedPlan* cplan) +{ + PlannedStmt* stmt = nullptr; + Plan* plan = nullptr; + TargetEntry* tle = nullptr; + + // Initialize to "not simple", and remember the plan generation number we last checked. + expr->expr_simple_expr = nullptr; + expr->expr_simple_generation = cplan->generation; + expr->expr_simple_need_snapshot = true; + + // 1. There must be one single plan tree + if (list_length(cplan->stmt_list) != 1) { + return false; + } + stmt = (PlannedStmt*)linitial(cplan->stmt_list); + // 2. It must be a RESULT plan --> no scan's required + if (!IsA(stmt, PlannedStmt)) { + return false; + } + if (stmt->commandType != CMD_SELECT) { + return false; + } + plan = stmt->planTree; + if (!IsA(plan, BaseResult)) { + return false; + } + + // 3. Can't have any subplan or qual clause, either + if (plan->lefttree != nullptr || plan->righttree != nullptr || plan->initPlan != nullptr || plan->qual != nullptr || + ((BaseResult*)plan)->resconstantqual != nullptr) { + return false; + } + + // 4. The plan must have a single attribute as result + if (list_length(plan->targetlist) != 1) { + return false; + } + tle = (TargetEntry*)linitial(plan->targetlist); + // 5. Check that all the nodes in the expression are non-scary. + if (!ExecSimpleCheckNode((Node*)tle->expr)) { + return false; + } + + // Yes - this is a simple expression. Mark it as such, and initialize state to "not valid in current transaction". + expr->expr_simple_expr = tle->expr; + expr->expr_simple_state = nullptr; + expr->expr_simple_in_use = false; + expr->expr_simple_lxid = InvalidLocalTransactionId; + // Also stash away the expression result type + expr->expr_simple_type = exprType((Node*)tle->expr); + return true; +} + +static bool CheckExprSimplePlan(PLpgSQL_expr* expr, ExprQueryAttrs* attrs) +{ + // Attention: expression has already been checked for having exactly one plan source containing one query tree + + /// Initialize to "not simple", and remember the plan generation number we last checked. (If we don't get as far + // as obtaining a plan to check, we just leave expr_simple_generation set to 0.) + expr->expr_simple_expr = nullptr; + expr->expr_simple_generation = 0; + expr->expr_simple_need_snapshot = true; + + // Do some checking on the analyzed-and-rewritten form of the query. These checks are basically redundant with the + // tests in exec_simple_recheck_plan, but the point is to avoid building a plan if possible. Since this function + // is only called immediately after creating the CachedPlanSource, we need not worry about the query being stale. + + Query* query = attrs->m_query; + + // 1. It must be a plain SELECT query without any input tables + if (!IsA(query, Query)) { + return false; + } + if (query->commandType != CMD_SELECT) { + return false; + } + if (query->rtable != NIL) { + return false; + } + + // 2. Can't have any subplans, aggregates, qual clauses either + if (query->hasAggs || query->hasWindowFuncs || query->hasSubLinks || query->hasForUpdate || query->cteList || + query->jointree->quals || query->groupClause || query->havingQual || query->windowClause || + query->distinctClause || query->sortClause || query->limitOffset || query->limitCount || query->setOperations) { + return false; + } + + // 3. The query must have a single attribute as result + if (list_length(query->targetList) != 1) { + return false; + } + + // OK, it seems worth constructing a plan for more careful checking. + + // Get the generic plan for the query + // Can't fail, because we have a single cached plan source + CachedPlan* cplan = SPI_plan_get_cached_plan(expr->plan); + MOT_ASSERT(cplan != nullptr); + + // Share the remaining work with recheck code path + bool result = RecheckExprSimplePlan(expr, cplan); + + // Release our plan ref-count + ReleaseCachedPlan(cplan, expr->plan->saved); + + return result; +} + +static llvm::Value* ProcessConstExpr(JitLlvmFunctionCodeGenContext* ctx, Const* constExpr, Oid* resultType) +{ + *resultType = constExpr->consttype; + llvm::Type* type = GetOidType(ctx, constExpr->consttype); + if (type == nullptr) { + MOT_LOG_TRACE("Unsupported constant type: %d", (int)constExpr->consttype); + return nullptr; + } + + llvm::Value* result = nullptr; + if (IsTypeSupported(constExpr->consttype) || (constExpr->consttype == UNKNOWNOID)) { + AddSetExprIsNull(ctx, JIT_CONST_INT32(constExpr->constisnull)); + AddSetExprCollation(ctx, JIT_CONST_INT32(constExpr->constcollid)); + if (IsPrimitiveType(constExpr->consttype)) { + result = JIT_CONST_INT64(constExpr->constvalue); + } else { + int constId = AllocateConstId(ctx, constExpr->consttype, constExpr->constvalue, constExpr->constisnull); + if (constId == -1) { + MOT_LOG_TRACE("Failed to allocate constant identifier"); + } else { + result = AddGetConstAt(ctx, constId); + } + } + } + return result; +} + +static llvm::Value* ProcessParamExpr(JitLlvmFunctionCodeGenContext* ctx, Param* param, Oid* resultType) +{ + // we expect external parameter kind, with index from 1 to N + llvm::Type* type = GetOidType(ctx, param->paramtype); + if (type == nullptr) { + MOT_LOG_TRACE("Unsupported parameter type: %d", (int)param->paramtype); + return nullptr; + } + + *resultType = param->paramtype; + + int varNo = param->paramid - 1; + JIT_DEBUG_LOG_INT("Retrieving param from varno: %d", varNo); + llvm::Value* var = GetIsNullLocalOrParamByVarNo(ctx, varNo); + llvm::Value* isNull = ctx->m_builder->CreateLoad(var, true); + JIT_DEBUG_LOG_INT_VAR("param is null value: %d", isNull); + llvm::Value* isNullInt = ctx->m_builder->CreateCast(llvm::Instruction::SExt, isNull, ctx->INT32_T); + AddSetExprIsNull(ctx, isNullInt); + AddSetExprCollation(ctx, JIT_CONST_INT32(param->paramcollid)); + + // this is a varno reference and not an input param reference + llvm::Value* result = GetLocalOrParamByVarNo(ctx, varNo); + return ctx->m_builder->CreateLoad(result, true); +} + +static llvm::Value* ProcessArrayRefExpr(JitLlvmFunctionCodeGenContext* ctx, ArrayRef* arrayRef, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported array ref expression"); + return nullptr; +} + +static llvm::Value* AddInvokePGFunction(JitLlvmFunctionCodeGenContext* ctx, Oid funcId, uint32_t argNum, + Oid funcCollationId, Oid resultCollationId, llvm::Value** args, llvm::Value** argIsNull, Oid* argTypes) +{ + uint32_t argCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(funcId, &argCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("AddInvokePGFunction(): Cannot find function by id: %u", (unsigned)funcId); + return nullptr; + } + // some functions (like text_concat) seem to be able to receive more arguments than stated, so we don't assert + if (argCount != argNum) { + MOT_LOG_TRACE("AddInvokePGFunction(): number of arguments passed %u does not equal function argument count %u", + argNum, + argCount); + } + + llvm::Value* result = nullptr; + if (argNum == 0) { + result = AddInvokePGFunction0(ctx, funcPtr, funcCollationId); + } else if (argNum == 1) { + result = AddInvokePGFunction1(ctx, funcPtr, funcCollationId, isStrict, args[0], argIsNull[0], argTypes[0]); + } else if (argNum == 2) { + result = AddInvokePGFunction2(ctx, + funcPtr, + funcCollationId, + isStrict, + args[0], + argIsNull[0], + argTypes[0], + args[1], + argIsNull[1], + argTypes[1]); + } else if (argNum == 3) { + result = AddInvokePGFunction3(ctx, + funcPtr, + funcCollationId, + isStrict, + args[0], + argIsNull[0], + argTypes[0], + args[1], + argIsNull[1], + argTypes[1], + args[2], + argIsNull[2], + argTypes[2]); + } else { + result = AddInvokePGFunctionN(ctx, funcPtr, funcCollationId, isStrict, args, argIsNull, argTypes, argCount); + } + CheckThrownException(ctx); + AddSetExprCollation(ctx, JIT_CONST_INT32(resultCollationId)); + return result; +} + +static llvm::Value* ProcessFuncCall(JitLlvmFunctionCodeGenContext* ctx, Oid funcId, List* args, Oid funcCollationId, + Oid resultCollationId, const char* exprName) +{ + llvm::Value* result = nullptr; + llvm::Value** argArray = nullptr; + llvm::Value** argIsNull = nullptr; + Oid* argTypes = nullptr; + int argCount = list_length(args); + if (argCount > 0) { + size_t allocSize = sizeof(llvm::Value*) * argCount; + argArray = (llvm::Value**)MOT::MemSessionAlloc(allocSize); + if (argArray == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d arguments in function expression", + allocSize, + argCount); + return nullptr; + } + + argIsNull = (llvm::Value**)MOT::MemSessionAlloc(allocSize); + if (argIsNull == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d argument nulls in function expression", + allocSize, + argCount); + MOT::MemSessionFree(argArray); + return nullptr; + } + + allocSize = sizeof(Oid) * argCount; + argTypes = (Oid*)MOT::MemSessionAlloc(allocSize); + if (argTypes == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile", + "Failed to allocate %u bytes for %d argument types in function expression", + allocSize, + argCount); + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + return nullptr; + } + + int argNum = 0; + Oid argResultType = 0; + ListCell* lc; + foreach (lc, args) { + Expr* subExpr = (Expr*)lfirst(lc); + argArray[argNum] = ProcessExpr(ctx, subExpr, &argResultType); + if (argArray[argNum] == nullptr) { + MOT_LOG_TRACE("Failed to process function sub-expression %d", argNum); + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + MOT::MemSessionFree(argTypes); + return nullptr; + } + argIsNull[argNum] = AddGetExprIsNull(ctx); + argTypes[argNum] = argResultType; + ++argNum; + } + MOT_ASSERT(argNum == argCount); + } + + result = + AddInvokePGFunction(ctx, funcId, argCount, funcCollationId, resultCollationId, argArray, argIsNull, argTypes); + + if (argCount > 0) { + MOT::MemSessionFree(argArray); + MOT::MemSessionFree(argIsNull); + MOT::MemSessionFree(argTypes); + } + + return result; +} + +static llvm::Value* ProcessFuncExpr(JitLlvmFunctionCodeGenContext* ctx, FuncExpr* funcExpr, Oid* resultType) +{ + *resultType = funcExpr->funcresulttype; + return ProcessFuncCall( + ctx, funcExpr->funcid, funcExpr->args, funcExpr->inputcollid, funcExpr->funccollid, "function"); +} + +static llvm::Value* ProcessOpExpr(JitLlvmFunctionCodeGenContext* ctx, OpExpr* opExpr, Oid* resultType) +{ + *resultType = opExpr->opresulttype; + return ProcessFuncCall(ctx, opExpr->opfuncid, opExpr->args, opExpr->inputcollid, opExpr->opcollid, "operator"); +} + +static llvm::Value* ProcessDistinctExpr(JitLlvmFunctionCodeGenContext* ctx, DistinctExpr* distinctExpr, Oid* resultType) +{ + // we should implement "x IS DISTINCT FROM y" + // the actual OpExpr provided implements an "equals" operator, and we should invert the result + // in addition, we can avoid calling the operator expression if any of the arguments is null, since the result can + // be computed directly + + // we expect exactly two parameters + int argCount = list_length(distinctExpr->args); + if (argCount != MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("Disqualifying SP: Distinct operator without two parameters, but rather %d", argCount); + return nullptr; + } + + *resultType = distinctExpr->opresulttype; + + // check if any of the arguments is null + Oid lhsResultType = InvalidOid; + Oid rhsResultType = InvalidOid; + Expr* lhs = (Expr*)linitial(distinctExpr->args); + Expr* rhs = (Expr*)lsecond(distinctExpr->args); + llvm::Value* lhsArg = ProcessExpr(ctx, lhs, &lhsResultType); + if (lhsArg == nullptr) { + MOT_LOG_TRACE("Failed to process distinct LHS expression"); + return nullptr; + } + llvm::Value* lhsArgIsNull = AddGetExprIsNull(ctx); + llvm::Value* rhsArg = ProcessExpr(ctx, rhs, &rhsResultType); + if (rhsArg == nullptr) { + MOT_LOG_TRACE("Failed to process distinct RHS expression"); + return nullptr; + } + llvm::Value* rhsArgIsNull = AddGetExprIsNull(ctx); + + // we must define a local variable for this to work properly + llvm::Value* distinctResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "distinct_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), distinctResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(distinctExpr->opcollid)); + + bool failed = false; + JIT_IF_BEGIN(distinct_op_lhs_null); + JIT_IF_EVAL(lhsArgIsNull); + { + // LHS arg is null: if rhs is also null then they are not distinct, so return 0, otherwise return 1 + llvm::Value* rhsArgIsNullBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, rhsArgIsNull, ctx->INT1_T); + llvm::Value* result = ctx->m_builder->CreateSelect( + rhsArgIsNullBool, JIT_CONST_INT64(BoolGetDatum(false)), JIT_CONST_INT64(BoolGetDatum(true))); + ctx->m_builder->CreateStore(result, distinctResult, true); + } + JIT_ELSE(); + { + // at this point we know that lhs arg is not null, so if rhs is null then they are definitely distinct + JIT_IF_BEGIN(distinct_op_rhs_null); + JIT_IF_EVAL(rhsArgIsNull); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), distinctResult, true); + } + JIT_ELSE(); + { + // at this point both are not null, so we compute the equals operator and invert the result + uint32_t funcArgCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(distinctExpr->opfuncid, &funcArgCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE( + "ProcessDistinctExpr(): Cannot find operator function by id: %u", (unsigned)distinctExpr->opfuncid); + failed = true; + } else { + MOT_ASSERT(funcArgCount == MOT_JIT_MAX_BOOL_EXPR_ARGS); + if (funcArgCount != MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE( + "ProcessDistinctExpr(): Invalid argument count %u for function %u in distinct operator", + funcArgCount, + (unsigned)distinctExpr->opfuncid); + failed = true; + } else { + // compute the equals operator and invert the result + llvm::Value* result = AddInvokePGFunction2(ctx, + funcPtr, + distinctExpr->inputcollid, + isStrict, + lhsArg, + lhsArgIsNull, + lhsResultType, + rhsArg, + rhsArgIsNull, + rhsResultType); + CheckThrownException(ctx); + llvm::Value* resultBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, result, ctx->INT1_T); + llvm::Value* inverseResult = ctx->m_builder->CreateSelect( + resultBool, JIT_CONST_INT64(BoolGetDatum(false)), JIT_CONST_INT64(BoolGetDatum(true))); + ctx->m_builder->CreateStore(inverseResult, distinctResult, true); + } + } + } + JIT_IF_END(); + } + JIT_IF_END(); + + if (failed) { + return nullptr; + } + return ctx->m_builder->CreateLoad(distinctResult, true); +} + +static llvm::Value* ProcessNullIfExpr(JitLlvmFunctionCodeGenContext* ctx, NullIfExpr* nullIfExpr, Oid* resultType) +{ + // code adapted from ExecEvalNullIf() in execQual.cpp + // we expect exactly two parameters + int argCount = list_length(nullIfExpr->args); + if (argCount != MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("Disqualifying SP: Null-if operator without two parameters, but rather %d", argCount); + return nullptr; + } + *resultType = nullIfExpr->opresulttype; + + // evaluate arguments + Oid lhsResultType = InvalidOid; + Oid rhsResultType = InvalidOid; + Expr* lhs = (Expr*)linitial(nullIfExpr->args); + Expr* rhs = (Expr*)lsecond(nullIfExpr->args); + llvm::Value* lhsArg = ProcessExpr(ctx, lhs, &lhsResultType); + if (lhsArg == nullptr) { + MOT_LOG_TRACE("Failed to process Null-if LHS expression"); + return nullptr; + } + llvm::Value* lhsArgIsNull = AddGetExprIsNull(ctx); + llvm::Value* rhsArg = ProcessExpr(ctx, rhs, &rhsResultType); + if (rhsArg == nullptr) { + MOT_LOG_TRACE("Failed to process Null-if RHS expression"); + return nullptr; + } + llvm::Value* rhsArgIsNull = AddGetExprIsNull(ctx); + + // we must define a local variable for this to work properly + llvm::Value* nullIfResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "nulif_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), nullIfResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(nullIfExpr->opcollid)); + + bool failed = false; + JIT_DEFINE_BLOCK(nullif_post_cmp); + + // if either argument is NULL they can't be equal + JIT_IF_BEGIN(nullif_op_lhs_null); + JIT_IF_EVAL_NOT(lhsArgIsNull); + { + JIT_IF_BEGIN(nullif_op_rhs_null); + JIT_IF_EVAL_NOT(rhsArgIsNull); + { + // at this point both are not null, so we compute the equals operator and invert the result + uint32_t funcArgCount = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(nullIfExpr->opfuncid, &funcArgCount, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE( + "ProcessNullIfExpr(): Cannot find operator function by id: %u", (unsigned)nullIfExpr->opfuncid); + failed = true; + } else { + MOT_ASSERT(funcArgCount == MOT_JIT_MAX_BOOL_EXPR_ARGS); + if (funcArgCount != MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("ProcessNullIfExpr(): Invalid argument count %u for function %u in distinct operator", + funcArgCount, + (unsigned)nullIfExpr->opfuncid); + failed = true; + } else { + // compute the equals operator and see if the result is non-null true + llvm::Value* result = AddInvokePGFunction2(ctx, + funcPtr, + nullIfExpr->inputcollid, + isStrict, + lhsArg, + lhsArgIsNull, + lhsResultType, + rhsArg, + rhsArgIsNull, + rhsResultType); + CheckThrownException(ctx); + llvm::Value* resIsNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(nullif_res_not_null); + JIT_IF_EVAL_NOT(resIsNull); + { + JIT_IF_BEGIN(nullif_res_value_true); + JIT_IF_EVAL(result); + { + // the arguments are equal, so return null + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + ctx->m_builder->CreateStore(JIT_CONST_INT64((Datum)0), nullIfResult, true); + JIT_GOTO(nullif_post_cmp); + } + JIT_IF_END(); + } + JIT_IF_END(); + } + } + } + JIT_IF_END(); + } + JIT_IF_END(); + + if (failed) { + return nullptr; + } + + // return the first argument + AddSetExprIsNull(ctx, lhsArgIsNull); + ctx->m_builder->CreateStore(lhsArg, nullIfResult, true); + + ctx->m_builder->SetInsertPoint(nullif_post_cmp); + return nullIfResult; +} + +static llvm::Value* ProcessScalarConstArrayOpExpr(JitLlvmFunctionCodeGenContext* ctx, ScalarArrayOpExpr* scalarExpr, + llvm::Value* lhsValue, llvm::Value* lhsIsNull, Oid lhsArgType, Const* constArrayExpr) +{ + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + Datum arrayDatum = constArrayExpr->constvalue; + ArrayType* arrayType = DatumGetArrayTypeP(arrayDatum); + int nitems = ArrayGetNItems(ARR_NDIM(arrayType), ARR_DIMS(arrayType)); + if (nitems <= 0) { + return JIT_CONST_INT64(BoolGetDatum(!scalarExpr->useOr)); + } + + // get the function pointer + uint32_t args = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(scalarExpr->opfuncid, &args, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE( + "ProcessScalarConstArrayOpExpr(): Cannot find function by id: %u", (unsigned)scalarExpr->opfuncid); + return nullptr; + } + MOT_ASSERT(args == 2); + + // get array element type data once + Oid elementType = ARR_ELEMTYPE(arrayType); + int16 typeLen = 0; + bool typeByVal = true; + char typeAlign = 0; + get_typlenbyvalalign(elementType, &typeLen, &typeByVal, &typeAlign); + if (!IsTypeSupported(elementType)) { + MOT_LOG_TRACE("ProcessScalarConstArrayOpExpr(): Constant array element type %u unsupported", elementType); + return nullptr; + } + + // we must define a local variable for this to work properly + llvm::Value* scalarResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "scalar_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(!scalarExpr->useOr)), scalarResult, true); + + // evaluate result + bool failed = false; + JIT_IF_BEGIN(scalar_lhs_is_null); + // if the scalar is NULL, and the function is strict, return NULL + // for simpler logic, we embed the isStrict constant in the generated code + llvm::BasicBlock* postIfBlock = JIT_IF_POST_BLOCK(); + llvm::Value* cond = ctx->m_builder->CreateSelect(JIT_CONST_INT1(isStrict), lhsIsNull, JIT_CONST_INT32(0)); + JIT_IF_EVAL(cond); + { + // if the scalar is NULL, and the function is strict, return NULL + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + } + JIT_ELSE(); + { + char* arrayData = (char*)ARR_DATA_PTR(arrayType); + bits8* bitmap = ARR_NULLBITMAP(arrayType); + int bitmask = 1; + + // iterate over array elements, evaluate each one, and compare to LHS value + for (int i = 0; i < nitems; i++) { + Datum elementValue = PointerGetDatum(nullptr); + bool isNull = true; + + // get array element, checking for NULL + if (bitmap && (*bitmap & bitmask) == 0) { + MOT_LOG_TRACE("Element %d is null", i); + } else { + elementValue = fetch_att(arrayData, typeByVal, typeLen); + arrayData = att_addlength_pointer(arrayData, typeLen, arrayData); + arrayData = (char*)att_align_nominal(arrayData, typeAlign); + isNull = false; + } + + // get the datum as a constant + int constId = AllocateConstId(ctx, elementType, elementValue, isNull); + if (constId == -1) { + MOT_LOG_TRACE("Failed to allocate constant identifier"); + failed = true; + break; + } + + llvm::Value* element = AddGetConstAt(ctx, constId); + llvm::Value* elementIsNull = JIT_CONST_INT32(isNull ? 1 : 0); + + // invoke function + llvm::Value* cmpRes = AddInvokePGFunction2(ctx, + funcPtr, + scalarExpr->inputcollid, + isStrict, + lhsValue, + lhsIsNull, + lhsArgType, + element, + elementIsNull, + elementType); + CheckThrownException(ctx); + llvm::Value* cmpResNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(cmp_res_null); + JIT_IF_EVAL(cmpResNull); + { + // if comparison result is null, then entire result is null + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + JIT_GOTO(postIfBlock); + } + JIT_ELSE(); + { + if (scalarExpr->useOr) { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), scalarResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } else { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL_NOT(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), scalarResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } + } + JIT_IF_END(); + + // advance bitmap pointer if any + if (bitmap != nullptr) { + bitmask <<= 1; + if (bitmask == 0x100) { + bitmap++; + bitmask = 1; + } + } + } + } + JIT_IF_END(); + + if (failed) { + return nullptr; + } + return ctx->m_builder->CreateLoad(scalarResult, true); +} + +static llvm::Value* ProcessScalarArrayOpExpr( + JitLlvmFunctionCodeGenContext* ctx, ScalarArrayOpExpr* scalarExpr, Oid* resultType) +{ + *resultType = BOOLOID; + MOT_ASSERT(list_length(scalarExpr->args) == 2); + Expr* lhsExpr = (Expr*)linitial(scalarExpr->args); + Oid lhsArgType = 0; + llvm::Value* lhsValue = ProcessExpr(ctx, lhsExpr, &lhsArgType); + if (lhsValue == nullptr) { + MOT_LOG_TRACE("Failed to parse LHS expression in Scalar Array Op"); + return nullptr; + } + llvm::Value* lhsIsNull = AddGetExprIsNull(ctx); + + // we might get array expression or constant text array expression + Expr* expr = (Expr*)lsecond(scalarExpr->args); + if (expr->type == T_Const) { + Const* constArrayExpr = (Const*)expr; + return ProcessScalarConstArrayOpExpr(ctx, scalarExpr, lhsValue, lhsIsNull, lhsArgType, constArrayExpr); + } else if (expr->type != T_ArrayExpr) { + MOT_LOG_TRACE("Failed to process scalar array operation: unsupported array node type %d", expr->type); + return nullptr; + } + + // do some simple checks first + ArrayExpr* arrayExpr = (ArrayExpr*)expr; + if (arrayExpr->multidims) { + MOT_LOG_TRACE("Unsupported multi-dimensional array in Scalar Array Op"); + return nullptr; + } + if (!IsTypeSupported(arrayExpr->element_typeid)) { + MOT_LOG_TRACE("Unsupported array element type %u in Scalar Array Op", (unsigned)arrayExpr->element_typeid); + return nullptr; + } + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + int nitems = list_length(arrayExpr->elements); + if (nitems <= 0) { + return JIT_CONST_INT64(BoolGetDatum(!scalarExpr->useOr)); + } + + // get the function pointer + uint32_t args = 0; + bool isStrict = false; + PGFunction funcPtr = GetPGFunctionInfo(scalarExpr->opfuncid, &args, &isStrict); + if (funcPtr == nullptr) { + MOT_LOG_TRACE("ProcessScalarArrayOpExpr(): Cannot find function by id: %u", (unsigned)scalarExpr->opfuncid); + return nullptr; + } + MOT_ASSERT(args == 2); + + // we must define a local variable for this to work properly + llvm::Value* scalarResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "scalar_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(!scalarExpr->useOr)), scalarResult, true); + + // evaluate result + bool failed = false; + JIT_IF_BEGIN(scalar_lhs_is_null); + // if the scalar is NULL, and the function is strict, return NULL + // for simpler logic, we embed the isStrict constant in the generated code + llvm::BasicBlock* postIfBlock = JIT_IF_POST_BLOCK(); + llvm::Value* cond = ctx->m_builder->CreateSelect(JIT_CONST_INT1(isStrict), lhsIsNull, JIT_CONST_INT32(0)); + JIT_IF_EVAL(cond); + { + // if the scalar is NULL, and the function is strict, return NULL + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + } + JIT_ELSE(); + { + // iterate over array elements, evaluate each one, and compare to LHS value + Oid elementType = InvalidOid; + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); // initially result is not null + ListCell* arg = nullptr; + foreach (arg, arrayExpr->elements) { + Expr* e = (Expr*)lfirst(arg); + llvm::Value* element = ProcessExpr(ctx, e, &elementType); + if (element == nullptr) { + MOT_LOG_TRACE("Failed to process distinct array element expression"); + failed = true; + break; + } + llvm::Value* elementIsNull = AddGetExprIsNull(ctx); + + // invoke function + llvm::Value* cmpRes = AddInvokePGFunction2(ctx, + funcPtr, + scalarExpr->inputcollid, + isStrict, + lhsValue, + lhsIsNull, + lhsArgType, + element, + elementIsNull, + elementType); + CheckThrownException(ctx); + llvm::Value* cmpResNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(cmp_res_null); + JIT_IF_EVAL(cmpResNull); + { + // if comparison result is null, then entire result is null + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + JIT_GOTO(postIfBlock); + } + JIT_ELSE(); + { + if (scalarExpr->useOr) { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), scalarResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } else { + JIT_IF_BEGIN(eval_cmp); + JIT_IF_EVAL_NOT(cmpRes); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), scalarResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + JIT_GOTO(postIfBlock); + } + JIT_IF_END(); + } + } + JIT_IF_END(); + } + } + JIT_IF_END(); + + if (failed) { + return nullptr; + } + return ctx->m_builder->CreateLoad(scalarResult, true); +} + +static llvm::Value* ProcessBoolAndExpr(JitLlvmFunctionCodeGenContext* ctx, BoolExpr* boolExpr) +{ + // if any of the arguments is false we stop evaluation + // if there was a null then the result is null + // otherwise the result is true + // and expression with no arguments evaluates to true + // we generate a label to implement goto instruction for one of the cases above + llvm::BasicBlock* boolAndPostBlock = + llvm::BasicBlock::Create(ctx->m_codeGen->context(), "eval_bool_post_bb", ctx->m_jittedFunction); + + // in order to process correctly nulls we need a local variable + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + llvm::Value* anyNulls = ctx->m_builder->CreateAlloca(ctx->INT32_T, 0, nullptr, "any_nulls"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), anyNulls, true); + ListCell* lc = nullptr; + Oid resultType = 0; + bool failed = false; + foreach (lc, boolExpr->args) { + llvm::Value* arg = ProcessExpr(ctx, (Expr*)lfirst(lc), &resultType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process argument expression in Boolean AND expression"); + failed = true; + break; + } + llvm::Value* isNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(arg_is_null); + JIT_IF_EVAL(isNull); + { + JIT_DEBUG_LOG("Encountered null argument in Boolean AND expression"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(1), anyNulls, true); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(arg_is_false); + JIT_IF_EVAL_NOT(arg); + { + JIT_DEBUG_LOG("Encountered false argument in Boolean AND expression"); + ctx->m_builder->CreateStore(arg, boolResult, true); + JIT_GOTO(boolAndPostBlock); + } + JIT_IF_END(); + } + JIT_IF_END(); + } + if (failed) { + return nullptr; + } + JIT_GOTO(boolAndPostBlock); + + ctx->m_builder->SetInsertPoint(boolAndPostBlock); + llvm::Value* ret = ctx->m_builder->CreateLoad(boolResult, true); + llvm::Value* retIsNull = ctx->m_builder->CreateLoad(anyNulls, true); + JIT_IF_BEGIN(result_is_true); + JIT_IF_EVAL_NOT(ret); + { + JIT_DEBUG_LOG("Boolean AND expression evaluated to false"); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(any_null_found); + JIT_IF_EVAL(retIsNull); + { + JIT_DEBUG_LOG("Boolean AND expression evaluated to true having nulls, setting false"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + } + JIT_ELSE(); + { + JIT_DEBUG_LOG("Boolean AND expression evaluated to true"); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + } + JIT_IF_END(); + } + JIT_IF_END(); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + return ctx->m_builder->CreateLoad(boolResult, true); +} + +static llvm::Value* ProcessBoolOrExpr(JitLlvmFunctionCodeGenContext* ctx, BoolExpr* boolExpr) +{ + // if any of the arguments is true we stop evaluation + // if there was a null then the result is null + // otherwise the result is true + // OR expression with no arguments evaluates to false + // we generate a label to implement goto instruction for one of the cases above + llvm::BasicBlock* boolOrPostBlock = + llvm::BasicBlock::Create(ctx->m_codeGen->context(), "eval_bool_post_bb", ctx->m_jittedFunction); + + // in order to process correctly nulls we need a local variable + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + llvm::Value* anyNulls = ctx->m_builder->CreateAlloca(ctx->INT32_T, 0, nullptr, "any_nulls"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(0), anyNulls, true); + ListCell* lc = nullptr; + Oid resultType = 0; + bool failed = false; + foreach (lc, boolExpr->args) { + llvm::Value* arg = ProcessExpr(ctx, (Expr*)lfirst(lc), &resultType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process argument expression in Boolean OR expression"); + failed = true; + break; + } + llvm::Value* isNull = AddGetExprIsNull(ctx); + JIT_IF_BEGIN(arg_is_null); + JIT_IF_EVAL(isNull); + { + JIT_DEBUG_LOG("Encountered null argument in Boolean OR expression"); + ctx->m_builder->CreateStore(JIT_CONST_INT32(1), anyNulls, true); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(arg_is_true); + JIT_IF_EVAL(arg); + { + JIT_DEBUG_LOG("Encountered true argument in Boolean OR expression"); + ctx->m_builder->CreateStore(arg, boolResult, true); + JIT_GOTO(boolOrPostBlock); + } + JIT_IF_END(); + } + JIT_IF_END(); + } + if (failed) { + return nullptr; + } + JIT_GOTO(boolOrPostBlock); + + ctx->m_builder->SetInsertPoint(boolOrPostBlock); + llvm::Value* ret = ctx->m_builder->CreateLoad(boolResult, true); + llvm::Value* retIsNull = ctx->m_builder->CreateLoad(anyNulls, true); + JIT_IF_BEGIN(result_is_true); + JIT_IF_EVAL(ret); + { + JIT_DEBUG_LOG("Boolean OR expression evaluated to true"); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(any_null_found); + JIT_IF_EVAL(retIsNull); + { + JIT_DEBUG_LOG("Boolean OR expression evaluated to false having nulls"); + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + } + JIT_ELSE(); + { + JIT_DEBUG_LOG("Boolean OR expression evaluated to false"); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + } + JIT_IF_END(); + } + JIT_IF_END(); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + return ret; +} + +static llvm::Value* ProcessBoolNotExpr(JitLlvmFunctionCodeGenContext* ctx, BoolExpr* boolExpr) +{ + MOT_ASSERT(list_length(boolExpr->args) == 1); + Oid resultType = 0; + llvm::Value* arg = ProcessExpr(ctx, (Expr*)linitial(boolExpr->args), &resultType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process Boolean NOT operator argument"); + return nullptr; + } + llvm::Value* argIsNull = AddGetExprIsNull(ctx); + + // in order to process correctly nulls we need a local variable + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + + JIT_IF_BEGIN(arg_is_null); + JIT_IF_EVAL(argIsNull); + { + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + } + JIT_ELSE(); + { + llvm::Value* cond = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, arg, ctx->INT1_T); + llvm::Value* result = ctx->m_builder->CreateSelect( + cond, JIT_CONST_INT64(BoolGetDatum(false)), JIT_CONST_INT64(BoolGetDatum(true))); + ctx->m_builder->CreateStore(result, boolResult); + } + JIT_IF_END(); + + return ctx->m_builder->CreateLoad(boolResult, true); +} + +static llvm::Value* ProcessBoolExpr(JitLlvmFunctionCodeGenContext* ctx, BoolExpr* boolExpr, Oid* resultType) +{ + *resultType = BOOLOID; + switch (boolExpr->boolop) { + case AND_EXPR: + return ProcessBoolAndExpr(ctx, boolExpr); + + case OR_EXPR: + return ProcessBoolOrExpr(ctx, boolExpr); + + case NOT_EXPR: + return ProcessBoolNotExpr(ctx, boolExpr); + + default: + MOT_LOG_TRACE("Invalid Boolean expression: %d", (int)boolExpr->boolop); + return nullptr; + } +} + +static llvm::Value* ProcessFieldSelect( + JitLlvmFunctionCodeGenContext* ctx, FieldSelect* field_select_expr, Oid* resultType) +{ + llvm::Value* row = ProcessExpr(ctx, field_select_expr->arg, resultType); + if (row == nullptr) { + MOT_LOG_TRACE("Failed to evaluate row argument of field-select expression"); + return nullptr; + } + + MOT_LOG_TRACE("Unsupported field-select expression"); + return nullptr; +} + +static llvm::Value* ProcessFieldStore(JitLlvmFunctionCodeGenContext* ctx, FieldStore* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported field-store expression"); + return nullptr; +} + +static llvm::Value* ProcessRelabelType(JitLlvmFunctionCodeGenContext* ctx, RelabelType* expr, Oid* resultType) +{ + // relabel type is a dummy no-op node used only for type coercion for binary compatible types, so we just need to + // execute the underlying node, and report back the type and collation specified in the relabel type node + llvm::Value* result = ProcessExpr(ctx, expr->arg, resultType); + if (result != nullptr) { + // override result type and collation id + *resultType = expr->resulttype; + AddSetExprCollation(ctx, JIT_CONST_INT32(expr->resultcollid)); + } + return result; +} + +static llvm::Value* ProcessCoerceViaIO(JitLlvmFunctionCodeGenContext* ctx, CoerceViaIO* expr, Oid* resultType) +{ + Oid argType = InvalidOid; + llvm::Value* arg = ProcessExpr(ctx, expr->arg, &argType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process coerce via IO expression input argument"); + return nullptr; + } + + // call conversion function + llvm::Value* result = + AddConvertViaString(ctx, arg, JIT_CONST_INT32(argType), JIT_CONST_INT32(expr->resulttype), JIT_CONST_INT32(-1)); + if (result != nullptr) { + *resultType = expr->resulttype; + AddSetExprCollation(ctx, JIT_CONST_INT32(expr->resultcollid)); + } + return result; +} + +static llvm::Value* ProcessArrayCoerceExpr(JitLlvmFunctionCodeGenContext* ctx, ArrayCoerceExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported array-coerce expression"); + return nullptr; +} + +static llvm::Value* ProcessConvertRowtypeExpr( + JitLlvmFunctionCodeGenContext* ctx, ConvertRowtypeExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported convert-row-type expression"); + return nullptr; +} + +static llvm::Value* ProcessCaseExpr(JitLlvmFunctionCodeGenContext* ctx, CaseExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported case expression"); + return nullptr; +} + +static llvm::Value* ProcessCaseWhenExpr(JitLlvmFunctionCodeGenContext* ctx, CaseWhen* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported case-when expression"); + return nullptr; +} + +static llvm::Value* ProcessCaseTestExpr(JitLlvmFunctionCodeGenContext* ctx, CaseTestExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported case-test expression"); + return nullptr; +} + +static llvm::Value* ProcessArrayExpr(JitLlvmFunctionCodeGenContext* ctx, ArrayExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported array expression"); + return nullptr; +} + +static llvm::Value* ProcessRowExpr(JitLlvmFunctionCodeGenContext* ctx, RowExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported row expression"); + return nullptr; +} + +static llvm::Value* ProcessRowCompareExpr(JitLlvmFunctionCodeGenContext* ctx, RowCompareExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported row-compare expression"); + return nullptr; +} + +static llvm::Value* ProcessCoalesceExpr(JitLlvmFunctionCodeGenContext* ctx, CoalesceExpr* expr, Oid* resultType) +{ + // evaluate expressions in list one by one, return the first non-null, otherwise return null + // we must define a local variable for this to work properly + llvm::Value* coalesceResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "coalesce_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(0), coalesceResult, true); + std::list ifStack; + ListCell* arg = nullptr; + bool result = true; + foreach (arg, expr->args) { + Expr* e = (Expr*)lfirst(arg); + llvm::Value* value = ProcessExpr(ctx, e, resultType); + if (value == nullptr) { + MOT_LOG_TRACE("Failed to parse COALESCE argument"); + result = false; + break; + } + llvm::Value* isNull = AddGetExprIsNull(ctx); + llvm_util::JitIf* jitIf = new (std::nothrow) llvm_util::JitIf(ctx, "coalesce"); + if (jitIf == nullptr) { + MOT_LOG_TRACE("Failed to allocate JIT IF statement"); + result = false; + break; + } + ifStack.push_back(jitIf); + jitIf->JitIfNot(isNull); + { + ctx->m_builder->CreateStore(value, coalesceResult, true); + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + } + jitIf->JitElse(); + } + // terminate last if expression by marking expression as evaluated to null + if (result) { + AddSetExprIsNull(ctx, JIT_CONST_INT32(1)); + AddSetExprCollation(ctx, JIT_CONST_INT32(expr->coalescecollid)); + } + while (!ifStack.empty()) { + ifStack.back()->JitEnd(); + llvm_util::JitIf* jitIf = ifStack.back(); + if (result) { + jitIf->JitEnd(); + } + delete jitIf; + ifStack.pop_back(); + } + llvm::Value* finalResult = nullptr; + if (result) { + finalResult = ctx->m_builder->CreateLoad(coalesceResult, true); + } + return finalResult; +} + +static llvm::Value* ProcessMinMaxExpr(JitLlvmFunctionCodeGenContext* ctx, MinMaxExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported min-max expression"); + return nullptr; +} + +static llvm::Value* ProcessXmlExpr(JitLlvmFunctionCodeGenContext* ctx, MinMaxExpr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported xml expression"); + return nullptr; +} + +static llvm::Value* ProcessNullTest(JitLlvmFunctionCodeGenContext* ctx, NullTest* expr, Oid* resultType) +{ + // we do not support null test for row type + if (expr->argisrow) { + MOT_LOG_TRACE("Disqualifying SP: unsupported null test for row type"); + return nullptr; + } + + // evaluate expression + llvm::Value* arg = ProcessExpr(ctx, expr->arg, resultType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process null test operator argument"); + return nullptr; + } + + // evaluate result + llvm::Value* argIsNull = AddGetExprIsNull(ctx); + llvm::Value* argIsNullBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, argIsNull, ctx->INT1_T); + + // mark current expression as not null and return result + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + if (expr->nulltesttype == IS_NULL) { + return ctx->m_builder->CreateSelect( + argIsNullBool, JIT_CONST_INT64(BoolGetDatum(true)), JIT_CONST_INT64(BoolGetDatum(false))); + } else if (expr->nulltesttype == IS_NOT_NULL) { + return ctx->m_builder->CreateSelect( + argIsNullBool, JIT_CONST_INT64(BoolGetDatum(false)), JIT_CONST_INT64(BoolGetDatum(true))); + } else { + MOT_LOG_TRACE("Unrecognized null-test type: %d", (int)expr->nulltesttype); + return nullptr; + } +} + +static llvm::Value* ProcessHashFilter(JitLlvmFunctionCodeGenContext* ctx, HashFilter* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported hash-filter expression"); + return nullptr; +} + +static llvm::Value* ProcessBooleanTest(JitLlvmFunctionCodeGenContext* ctx, BooleanTest* expr, Oid* resultType) +{ + // evaluate expression + llvm::Value* arg = ProcessExpr(ctx, expr->arg, resultType); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to process Boolean test operator argument"); + return nullptr; + } + + // evaluate result + llvm::Value* argIsNull = AddGetExprIsNull(ctx); + + // mark current expression as not null and return result + AddSetExprIsNull(ctx, JIT_CONST_INT32(0)); + AddSetExprCollation(ctx, JIT_CONST_INT32(InvalidOid)); + + // we must define a local variable for this to work properly + llvm::Value* boolResult = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "bool_result"); + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(false)), boolResult, true); + + // by default the result is false, we relate only to changes into true value + switch (expr->booltesttype) { + case IS_TRUE: { + JIT_IF_BEGIN(bool_is_true); + JIT_IF_EVAL_NOT(argIsNull); + { + JIT_IF_BEGIN(eval_arg); + JIT_IF_EVAL(arg); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + } + JIT_IF_END(); + break; + } + + case IS_NOT_TRUE: { + JIT_IF_BEGIN(bool_is_not_true); + JIT_IF_EVAL(argIsNull); + { + // null value is not true, so result is true + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(eval_arg); + JIT_IF_EVAL_NOT(arg); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + } + JIT_IF_END(); + break; + } + + case IS_FALSE: { + // null value is not considered false + JIT_IF_BEGIN(bool_is_false); + JIT_IF_EVAL_NOT(argIsNull); + { + JIT_IF_BEGIN(eval_arg); + JIT_IF_EVAL_NOT(arg); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + } + JIT_IF_END(); + break; + } + + case IS_NOT_FALSE: { + // null value is not considered false + JIT_IF_BEGIN(bool_is_not_false); + JIT_IF_EVAL(argIsNull); + { + // null value is not true, so result is true + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_ELSE(); + { + JIT_IF_BEGIN(eval_arg); + JIT_IF_EVAL(arg); + { + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + } + JIT_IF_END(); + break; + } + + case IS_UNKNOWN: { + // null is considered unknown + JIT_IF_BEGIN(bool_is_not_false); + JIT_IF_EVAL(argIsNull); + { + // null value is not true, so result is true + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + break; + } + + case IS_NOT_UNKNOWN: { + // null is considered unknown + JIT_IF_BEGIN(bool_is_not_false); + JIT_IF_EVAL_NOT(argIsNull); + { + // null value is not true, so result is true + ctx->m_builder->CreateStore(JIT_CONST_INT64(BoolGetDatum(true)), boolResult, true); + } + JIT_IF_END(); + break; + } + + default: + MOT_LOG_TRACE("Unrecognized booltesttype: %d", (int)expr->booltesttype); + return nullptr; + } + + return ctx->m_builder->CreateLoad(boolResult, true); +} + +static llvm::Value* ProcessCoerceToDomain(JitLlvmFunctionCodeGenContext* ctx, CoerceToDomain* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported coerce-to-domain expression"); + return nullptr; +} + +static llvm::Value* ProcessCoerceToDomainValue( + JitLlvmFunctionCodeGenContext* ctx, CoerceToDomainValue* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported coerce-to-domain-value expression"); + return nullptr; +} + +static llvm::Value* ProcessExpr(JitLlvmFunctionCodeGenContext* ctx, Expr* expr, Oid* resultType) +{ + switch (nodeTag(expr)) { + case T_Const: + return ProcessConstExpr(ctx, (Const*)expr, resultType); + + case T_Param: + return ProcessParamExpr(ctx, (Param*)expr, resultType); + + case T_ArrayRef: + return ProcessArrayRefExpr(ctx, (ArrayRef*)expr, resultType); + + case T_FuncExpr: + return ProcessFuncExpr(ctx, (FuncExpr*)expr, resultType); + + case T_OpExpr: + return ProcessOpExpr(ctx, (OpExpr*)expr, resultType); + + case T_DistinctExpr: + return ProcessDistinctExpr(ctx, (DistinctExpr*)expr, resultType); + + case T_NullIfExpr: + return ProcessNullIfExpr(ctx, (NullIfExpr*)expr, resultType); + + case T_ScalarArrayOpExpr: + return ProcessScalarArrayOpExpr(ctx, (ScalarArrayOpExpr*)expr, resultType); + + case T_BoolExpr: + return ProcessBoolExpr(ctx, (BoolExpr*)expr, resultType); + + case T_FieldSelect: + return ProcessFieldSelect(ctx, (FieldSelect*)expr, resultType); + + case T_FieldStore: + return ProcessFieldStore(ctx, (FieldStore*)expr, resultType); + + case T_RelabelType: + return ProcessRelabelType(ctx, (RelabelType*)expr, resultType); + + case T_CoerceViaIO: + return ProcessCoerceViaIO(ctx, (CoerceViaIO*)expr, resultType); + + case T_ArrayCoerceExpr: + return ProcessArrayCoerceExpr(ctx, (ArrayCoerceExpr*)expr, resultType); + + case T_ConvertRowtypeExpr: + return ProcessConvertRowtypeExpr(ctx, (ConvertRowtypeExpr*)expr, resultType); + + case T_CaseExpr: + return ProcessCaseExpr(ctx, (CaseExpr*)expr, resultType); + + case T_CaseWhen: + return ProcessCaseWhenExpr(ctx, (CaseWhen*)expr, resultType); + + case T_CaseTestExpr: + return ProcessCaseTestExpr(ctx, (CaseTestExpr*)expr, resultType); + + case T_ArrayExpr: + return ProcessArrayExpr(ctx, (ArrayExpr*)expr, resultType); + + case T_RowExpr: + return ProcessRowExpr(ctx, (RowExpr*)expr, resultType); + + case T_RowCompareExpr: + return ProcessRowCompareExpr(ctx, (RowCompareExpr*)expr, resultType); + + case T_CoalesceExpr: + return ProcessCoalesceExpr(ctx, (CoalesceExpr*)expr, resultType); + + case T_MinMaxExpr: + return ProcessMinMaxExpr(ctx, (MinMaxExpr*)expr, resultType); + + case T_XmlExpr: + return ProcessXmlExpr(ctx, (MinMaxExpr*)expr, resultType); + + case T_NullTest: + return ProcessNullTest(ctx, (NullTest*)expr, resultType); + + case T_HashFilter: + return ProcessHashFilter(ctx, (HashFilter*)expr, resultType); + + case T_BooleanTest: + return ProcessBooleanTest(ctx, (BooleanTest*)expr, resultType); + + case T_CoerceToDomain: + return ProcessCoerceToDomain(ctx, (CoerceToDomain*)expr, resultType); + + case T_CoerceToDomainValue: + return ProcessCoerceToDomainValue(ctx, (CoerceToDomainValue*)expr, resultType); + + case T_List: + MOT_LOG_TRACE("Unexpected list expression"); + break; + + default: + MOT_LOG_TRACE("Unexpected expression type %d", (int)expr->type); + break; + } + + return nullptr; +} + +static llvm::Value* ProcessSimpleExpr(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, Oid* resultType) +{ + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + char* parsed_query = nodeToString(expr->expr_simple_expr); + MOT_LOG_TRACE("Processing simple expression:\n%s", parsed_query); + pfree(parsed_query); + } + return ProcessExpr(ctx, expr->expr_simple_expr, resultType); +} + +static llvm::Value* ProcessComplexExpr(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, Oid* resultType) +{ + MOT_LOG_TRACE("Unsupported complex expression"); + return nullptr; +} + +extern llvm::Value* ProcessExpr(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, Oid* resultType) +{ + llvm::Value* result = nullptr; + + // code adapted from pl_exec.cpp + + // work on copy + PLpgSQL_expr exprCopy = *expr; + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(&exprCopy, ctx->_compiled_function, &attrs)) { + MOT_LOG_TRACE("Failed to process expression: failed to get query attributes"); + return nullptr; + } + exprCopy.plan = attrs.m_spiPlan; + if (!CheckExprSimplePlan(&exprCopy, &attrs)) { + MOT_LOG_TRACE("Failed to process expression: expression is not simple"); + CleanupExprQueryAttrs(&attrs); + return nullptr; + } + + if (exprCopy.expr_simple_expr) { + result = ProcessSimpleExpr(ctx, &exprCopy, resultType); + } else { + result = ProcessComplexExpr(ctx, &exprCopy, resultType); + } + CleanupExprQueryAttrs(&attrs); + + return result; +} + +static bool ProcessStatementAssign(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_assign* stmt) +{ + MOT_LOG_TRACE("Processing statement: assign"); + return AssignValue(ctx, stmt->varno, stmt->expr); +} + +static bool ProcessStatementIf(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_if* stmt) +{ + MOT_LOG_TRACE("Processing statement: if"); + Oid resultType = 0; + llvm::Value* cond = ProcessExpr(ctx, stmt->cond, &resultType); + if (cond == nullptr) { + MOT_LOG_TRACE("Failed to process if statement: Failed to process condition expression"); + return false; + } + + bool result = true; + + JIT_IF_BEGIN(if_stmt); + JIT_IF_EVAL(cond); + { + if (!ProcessStatementList(ctx, stmt->then_body)) { + MOT_LOG_TRACE("Failed to process if statement: Failed to process then-body statement list"); + result = false; + } + } + JIT_ELSE(); + { + std::list ifStack; + ListCell* lc = nullptr; + foreach (lc, stmt->elsif_list) { + PLpgSQL_if_elsif* elsifBlock = (PLpgSQL_if_elsif*)lfirst(lc); + cond = ProcessExpr(ctx, elsifBlock->cond, &resultType); + if (cond == nullptr) { + MOT_LOG_TRACE("Failed to process if statement: Failed to process elsif condition expression"); + result = false; + break; + } + llvm_util::JitIf* jitIf = new (std::nothrow) llvm_util::JitIf(ctx, "elsif_stmt"); + if (jitIf == nullptr) { + MOT_LOG_TRACE("Failed to create JIT IF statement"); + result = false; + break; + } + jitIf->JitIfEval(cond); + if (!ProcessStatementList(ctx, elsifBlock->stmts)) { + MOT_LOG_TRACE("Failed to process if statement: Failed to process elsif-body statement list"); + result = false; + break; + } + jitIf->JitElse(); + ifStack.push_back(jitIf); + } + if (result && stmt->else_body) { + if (!ProcessStatementList(ctx, stmt->else_body)) { + MOT_LOG_TRACE("Failed to process if statement: Failed to process else-body statement list"); + result = false; + } + } + while (!ifStack.empty()) { + llvm_util::JitIf* jitIf = ifStack.back(); + if (result) { + jitIf->JitEnd(); + } + delete jitIf; + ifStack.pop_back(); + } + } + JIT_IF_END(); + + return result; +} + +static bool ProcessStatementGoto(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_goto* stmt) +{ + bool result = false; + + MOT_LOG_TRACE("Processing statement: goto"); + GotoLabelMap::iterator itr = ctx->m_gotoLabels.find(stmt->label); + if (itr == ctx->m_gotoLabels.end()) { + MOT_LOG_TRACE("Cannot jump to label %s: block label not found", stmt->label); + } else { + llvm::BasicBlock* block = itr->second; + ctx->m_builder->CreateBr(block); + result = true; + } + + return result; +} + +static bool ProcessStatementCase(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_case* stmt) +{ + // since case labels are translated into SQL queries (sometimes simplified into expressions) + // we actually need here an if-else JIT statement + + MOT_LOG_TRACE("Processing statement: switch-case"); + JIT_DEBUG_LOG("Processing statement: CASE-WHEN"); + llvm::Value* assignee = GetLocalOrParamByVarNo(ctx, stmt->t_varno); + if (!assignee) { + MOT_LOG_TRACE( + "Failed to process switch-case statement: Failed to find local assignee by varno %d", stmt->t_varno); + return false; + } + + llvm::Value* assigneeIsNull = GetIsNullLocalOrParamByVarNo(ctx, stmt->t_varno); + if (!assigneeIsNull) { + MOT_LOG_TRACE( + "Failed to process switch-case statement: Failed to find local assignee nulls by varno %d", stmt->t_varno); + return false; + } + Oid resultType = 0; + llvm::Value* switchValue = nullptr; + if (stmt->t_expr) { // non-searched CASE-WHEN syntax + switchValue = ProcessExpr(ctx, stmt->t_expr, &resultType); + if (switchValue == nullptr) { + MOT_LOG_TRACE("Failed to process switch-case statement: Failed to process switch value expression"); + return false; + } + if (resultType == INT4OID) { + JIT_DEBUG_LOG_INT_VAR("CASE-WHEN switch var value: %d", switchValue); + } + + llvm::Value* switchValueIsNull = AddGetExprIsNull(ctx); + llvm::Value* isNullBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, switchValueIsNull, ctx->BOOL_T); + llvm::StoreInst* storeInstNull = ctx->m_builder->CreateStore(isNullBool, assigneeIsNull, true); + if (!storeInstNull) { + MOT_LOG_TRACE("Failed to store switch is null value"); + return false; + } + // the switch value must be stored in the specified variable, since subsequent case expressions + // are actually if statements which refer to this stored value + llvm::StoreInst* storeInst = ctx->m_builder->CreateStore(switchValue, assignee, true); + if (!storeInst) { + MOT_LOG_TRACE("Failed to store switch value"); + return false; + } + } else { + JIT_DEBUG_LOG("CASE-WHEN searched syntax"); + } + + bool result = true; + + // begin switch-case block + std::list ifStack; + ListCell* lc = nullptr; + foreach (lc, stmt->case_when_list) { + PLpgSQL_case_when* caseWhenBlock = (PLpgSQL_case_when*)lfirst(lc); + llvm::Value* caseCond = ProcessExpr(ctx, caseWhenBlock->expr, &resultType); + if (caseCond == nullptr) { + MOT_LOG_TRACE("Failed to process switch-case statement: Failed to process elsif condition expression"); + result = false; + break; + } + + if (resultType == INT4OID) { + JIT_DEBUG_LOG_INT_VAR("CASE-WHEN condition value: %d", caseCond); + } + + llvm_util::JitIf* jitIf = new (std::nothrow) llvm_util::JitIf(ctx, "stmt_case"); + if (jitIf == nullptr) { + MOT_LOG_TRACE("Failed to create JIT IF statement"); + result = false; + break; + } + ifStack.push_back(jitIf); + jitIf->JitIfEval(caseCond); + + JIT_DEBUG_LOG("CASE-WHEN condition passed"); + + if (!ProcessStatementList(ctx, caseWhenBlock->stmts)) { + MOT_LOG_TRACE("Failed to process switch-case statement: Failed to process case-when-body statement list"); + result = false; + break; + } + jitIf->JitElse(); + JIT_DEBUG_LOG("CASE-WHEN condition rejected"); + } + if (stmt->have_else && (stmt->else_stmts != nullptr)) { + JIT_DEBUG_LOG("CASE-WHEN else body"); + if (!ProcessStatementList(ctx, stmt->else_stmts)) { + MOT_LOG_TRACE("Failed to process switch-case statement: Failed to process else-body statement list"); + result = false; + } + } + while (!ifStack.empty()) { + llvm_util::JitIf* jitIf = ifStack.back(); + if (result) { + jitIf->JitEnd(); + } + delete jitIf; + ifStack.pop_back(); + } + JIT_DEBUG_LOG("CASE-WHEN done"); + + return result; +} + +static bool ProcessStatementLoop(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_loop* stmt) +{ + MOT_LOG_TRACE("Processing statement: loop"); + bool result = ProcessStatementLabel(ctx, stmt->label, "unnamed_loop"); + if (result) { + // unconditional loop + JIT_DEBUG_LOG("LOOP begin"); + JIT_WHILE_BEGIN(stmt_loop) + LabelDesc labelDesc = {stmt->label, JIT_WHILE_COND_BLOCK(), JIT_WHILE_POST_BLOCK(), true}; + ctx->m_labelStack.push_back(labelDesc); + JIT_WHILE_EVAL(JIT_CONST_INT64(1)); + { + if (!ProcessStatementList(ctx, stmt->body)) { + MOT_LOG_TRACE("Failed to process loop statement: Failed to process loop-body statement list"); + result = false; + } + } + JIT_WHILE_END() + ctx->m_labelStack.pop_back(); + JIT_DEBUG_LOG("LOOP end"); + } + + return result; +} + +static bool ProcessStatementWhile(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_while* stmt) +{ + MOT_LOG_TRACE("Processing statement: while"); + bool result = ProcessStatementLabel(ctx, stmt->label, "unnamed_while"); + if (result) { + // conditional loop + JIT_WHILE_BEGIN(stmt_while) + LabelDesc labelDesc = {stmt->label, JIT_WHILE_COND_BLOCK(), JIT_WHILE_POST_BLOCK(), true}; + ctx->m_labelStack.push_back(labelDesc); + // attention: the condition expression evaluation code must be emitted after JIT_WHILE_BEGIN() + Oid resultType = 0; + llvm::Value* condValue = ProcessExpr(ctx, stmt->cond, &resultType); + if (condValue == nullptr) { + MOT_LOG_TRACE("Failed to process while statement: Failed to process condition value expression"); + result = false; + } + + if (result) { + JIT_WHILE_EVAL(condValue); + { + if (!ProcessStatementList(ctx, stmt->body)) { + MOT_LOG_TRACE("Failed to process while statement: Failed to process while-body statement list"); + result = false; + } + } + } + JIT_WHILE_END() + ctx->m_labelStack.pop_back(); + } + + return result; +} + +static bool ProcessStatementForI(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fori* stmt) +{ + MOT_LOG_TRACE("Processing statement: fori"); + bool result = ProcessStatementLabel(ctx, stmt->label, "unnamed_fori"); + if (result) { + // evaluate lower bound + Oid resultType = 0; + llvm::Value* lowerBound = ProcessExpr(ctx, stmt->lower, &resultType); + if (lowerBound == nullptr) { + MOT_LOG_TRACE("Failed to process for-integer statement: Failed to process lower bound expression"); + return false; + } + llvm::Value* lowerBoundIsNull = AddGetExprIsNull(ctx); + + // convert type if needed + if (resultType != stmt->var->datatype->typoid) { + // we need local variable in order to reuse AssignScalarValue + llvm::Value* lowerBoundValueLocal = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "lower_bound"); + llvm::Value* lowerBoundIsNullLocal = + ctx->m_builder->CreateAlloca(ctx->BOOL_T, 0, nullptr, "lower_bound_is_null"); + if (!AssignScalarValue(ctx, + stmt->var, + lowerBoundValueLocal, + lowerBoundIsNullLocal, + lowerBound, + lowerBoundIsNull, + resultType)) { + MOT_LOG_TRACE("ProcessStatementForI(): For to convert loop value") + return false; + } + lowerBound = ctx->m_builder->CreateLoad(lowerBoundValueLocal, true); + lowerBoundIsNull = ctx->m_builder->CreateLoad(lowerBoundIsNullLocal, true); + } + + // check in run time whether lower bound is null + JIT_IF_BEGIN(lower_bound_is_null); + JIT_IF_EVAL(lowerBoundIsNull); + { + AddSetSqlStateAndErrorMessage( + ctx, "lower bound of FOR loop cannot be null", ERRCODE_NULL_VALUE_NOT_ALLOWED); + JIT_DEBUG_LOG("JIT SP exception: lower bound of FOR loop cannot be null"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_NULL_VALUE_NOT_ALLOWED)); + } + JIT_IF_END(); + llvm::Value* loopValue = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "loop_value"); + ctx->m_builder->CreateStore(lowerBound, loopValue, true); + JIT_DEBUG_LOG_DATUM("Lower bound is", lowerBound, JIT_CONST_INT32(0), INT4OID); + + // evaluate upper bound + llvm::Value* upperBound = ProcessExpr(ctx, stmt->upper, &resultType); + if (upperBound == nullptr) { + MOT_LOG_TRACE("Failed to process for-integer statement: Failed to process upper bound expression"); + return false; + } + llvm::Value* upperBoundIsNull = AddGetExprIsNull(ctx); + + // convert type if needed + if (resultType != stmt->var->datatype->typoid) { + // we need local variable in order to reuse AssignScalarValue + llvm::Value* upperBoundLocal = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "upper_bound"); + llvm::Value* upperBoundIsNullLocal = + ctx->m_builder->CreateAlloca(ctx->BOOL_T, 0, nullptr, "upper_bound_is_null"); + if (!AssignScalarValue( + ctx, stmt->var, upperBoundLocal, upperBoundIsNullLocal, upperBound, upperBoundIsNull, resultType)) { + MOT_LOG_TRACE("ProcessStatementForI(): For to convert bound value") + return false; + } + upperBound = ctx->m_builder->CreateLoad(upperBoundLocal, true); + upperBoundIsNull = ctx->m_builder->CreateLoad(upperBoundIsNullLocal, true); + } + + // check in run time whether upper bound is null + JIT_IF_BEGIN(upper_bound_is_null); + JIT_IF_EVAL(upperBoundIsNull); + { + AddSetSqlStateAndErrorMessage( + ctx, "upper bound of FOR loop cannot be null", ERRCODE_NULL_VALUE_NOT_ALLOWED); + JIT_DEBUG_LOG("JIT SP exception: upper bound of FOR loop cannot be null"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_NULL_VALUE_NOT_ALLOWED)); + } + JIT_IF_END(); + JIT_DEBUG_LOG_DATUM("Upper bound is", upperBound, JIT_CONST_INT32(0), INT4OID); + + // evaluate step + llvm::Value* step = nullptr; + if (stmt->step != nullptr) { + step = ProcessExpr(ctx, stmt->step, &resultType); + if (step == nullptr) { + MOT_LOG_TRACE("Failed to process for-integer statement: Failed to process step expression"); + return false; + } + llvm::Value* stepIsNull = AddGetExprIsNull(ctx); + + // convert type if needed + if (resultType != stmt->var->datatype->typoid) { + // we need local variable in order to reuse AssignScalarValue + llvm::Value* stepLocal = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "step"); + llvm::Value* stepIsNullLocal = ctx->m_builder->CreateAlloca(ctx->BOOL_T, 0, nullptr, "step_is_null"); + if (!AssignScalarValue(ctx, stmt->var, stepLocal, stepIsNullLocal, step, stepIsNull, resultType)) { + MOT_LOG_TRACE("ProcessStatementForI(): For to convert bound value") + return false; + } + step = ctx->m_builder->CreateLoad(stepLocal, true); + stepIsNull = ctx->m_builder->CreateLoad(stepIsNullLocal, true); + } + + // check in run time whether upper bound is null + JIT_IF_BEGIN(step_is_null); + JIT_IF_EVAL(stepIsNull); + { + AddSetSqlStateAndErrorMessage( + ctx, "BY value of FOR loop cannot be null", ERRCODE_NULL_VALUE_NOT_ALLOWED); + JIT_DEBUG_LOG("JIT SP exception: BY value of FOR loop cannot be null"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_NULL_VALUE_NOT_ALLOWED)); + } + JIT_IF_END(); + JIT_IF_BEGIN(step_is_negative); + JIT_IF_EVAL_CMP(step, JIT_CONST_INT64(0), JitICmpOp::JIT_ICMP_LE); + { + AddSetSqlStateAndErrorMessage( + ctx, "BY value of FOR loop must be greater than zero", ERRCODE_INVALID_PARAMETER_VALUE); + JIT_DEBUG_LOG("JIT SP exception: BY value of FOR loop must be greater than zero"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_INVALID_PARAMETER_VALUE)); + } + JIT_IF_END(); + } else { + // if none is specified then the step is 1, just make sure it has the same type as the loop counter/bounds + step = llvm::ConstantInt::get(lowerBound->getType(), 1, true); + } + if (step == nullptr) { + MOT_LOG_TRACE("Failed to process for-integer statement: Failed to process step expression"); + return false; + } + JIT_DEBUG_LOG_DATUM("Step is", step, JIT_CONST_INT32(0), INT4OID); + + // define local found variable and initialize to false + llvm::Value* foundLocal = ctx->m_builder->CreateAlloca(ctx->DATUM_T, 0, nullptr, "found"); + llvm::Value* falseDatum = llvm::ConstantInt::get(ctx->DATUM_T, BoolGetDatum(false), false); + ctx->m_builder->CreateStore(falseDatum, foundLocal, true); + + // execute loop + JIT_WHILE_BEGIN(stmt_fori); + JIT_WHILE_EVAL(JIT_CONST_INT32(1)); + { + // check for loop end condition + llvm::Value* currLoopValue = ctx->m_builder->CreateLoad(loopValue, true); + JIT_DEBUG_LOG_DATUM("Current loop value is", currLoopValue, JIT_CONST_INT32(0), INT4OID); + JIT_IF_BEGIN(end_loop); + JIT_IF_EVAL_CMP(currLoopValue, upperBound, stmt->reverse ? JitICmpOp::JIT_ICMP_LT : JitICmpOp::JIT_ICMP_GT); + { + JIT_DEBUG_LOG("Reached loop bound, breaking from loop"); + JIT_WHILE_BREAK(); + } + JIT_IF_END(); + + // remember loop executed at least once + JIT_DEBUG_LOG("Storing true in FOUND"); + llvm::Value* trueDatum = llvm::ConstantInt::get(ctx->DATUM_T, BoolGetDatum(true), false); + ctx->m_builder->CreateStore(trueDatum, foundLocal, true); + + // assign loop value (so it can be accessed by other statements in the loop) + JIT_DEBUG_LOG("Storing current loop value in loop variable"); + llvm::Value* loopLocalValue = GetLocalOrParamByVarNo(ctx, stmt->var->dno); + llvm::Value* loopIsNullLocal = GetIsNullLocalOrParamByVarNo(ctx, stmt->var->dno); + ctx->m_builder->CreateStore(currLoopValue, loopLocalValue, true); + ctx->m_builder->CreateStore(JIT_CONST_BOOL(false), loopIsNullLocal, true); + + // execute loop statements + JIT_DEBUG_LOG("Processing loop statements"); + if (!ProcessStatementList(ctx, stmt->body)) { + MOT_LOG_TRACE("Failed to process for-integer statement: Failed to process for-body statement list"); + result = false; + } + + if (ctx->m_builder->GetInsertBlock()->getTerminator() == nullptr) { + JIT_DEBUG_LOG("Incrementing loop counter"); + if (stmt->reverse) { + llvm::Value* nextLoopValue = ctx->m_builder->CreateSub(currLoopValue, step); + JIT_IF_BEGIN(loop_bound_overflow); + JIT_IF_EVAL_CMP(nextLoopValue, currLoopValue, JitICmpOp::JIT_ICMP_GT); + { + JIT_DEBUG_LOG("Loop value overflow"); + JIT_WHILE_BREAK(); + } + JIT_ELSE(); + { + JIT_DEBUG_LOG_DATUM( + "Storing in loop value next value", nextLoopValue, JIT_CONST_INT32(0), INT4OID); + ctx->m_builder->CreateStore(nextLoopValue, loopValue, true); + } + JIT_IF_END(); + } else { + llvm::Value* nextLoopValue = ctx->m_builder->CreateAdd(currLoopValue, step); + JIT_IF_BEGIN(loop_bound_overflow); + JIT_IF_EVAL_CMP(nextLoopValue, currLoopValue, JitICmpOp::JIT_ICMP_LT); + { + JIT_DEBUG_LOG("Loop value underflow"); + JIT_WHILE_BREAK(); + } + JIT_ELSE(); + { + JIT_DEBUG_LOG_DATUM( + "Storing in loop value next value", nextLoopValue, JIT_CONST_INT32(0), INT4OID); + ctx->m_builder->CreateStore(nextLoopValue, loopValue, true); + } + JIT_IF_END(); + } + } else { + MOT_LOG_TRACE( + "Skipping code generation for incrementing loop counter: current block ends with terminator"); + } + } + JIT_WHILE_END(); + + // set found variable + llvm::Value* finalFoundValue = ctx->m_builder->CreateLoad(foundLocal, true); + llvm::Value* foundValue = GetLocalOrParamByVarNo(ctx, ctx->_compiled_function->found_varno); + ctx->m_builder->CreateStore(finalFoundValue, foundValue, true); + } + + return result; +} + +static bool ProcessStatementForS(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fors* stmt) +{ + MOT_LOG_TRACE("Processing statement: fors"); + return false; +} + +static bool ProcessStatementForC(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_forc* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: forc"); + return false; +} + +static bool ProcessStatementForEach(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_foreach_a* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: foreach"); + return false; +} + +static bool ProcessStatementExitGoto(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_exit* stmt) +{ + if (!stmt->label) { + if (JIT_LOOP_CURRENT() == nullptr) { + MOT_LOG_WARN("CONTINUE/BREAK cannot be used outside of a loop"); + return false; + } + if (stmt->is_exit) { + JIT_LOOP_BREAK(); // break innermost loop + } else { + JIT_LOOP_CONTINUE(); // continue to next iteration on innermost loop + } + return true; + } else { + if (stmt->is_exit) { + // branch to labeled end-of outer loop or block + // search in the label stack the named loop or block + LabelStack::reverse_iterator itr = ctx->m_labelStack.rbegin(); + while (itr != ctx->m_labelStack.rend()) { + LabelDesc& labelDesc = *itr; + if (strcmp(labelDesc.m_name, stmt->label) == 0) { + JIT_GOTO(labelDesc.m_postBlock); + return true; + } + ++itr; + } + } else { + // loop represents any outer loop name + LabelStack::reverse_iterator itr = ctx->m_labelStack.rbegin(); + while (itr != ctx->m_labelStack.rend()) { + LabelDesc& labelDesc = *itr; + if (labelDesc.m_isLoop && labelDesc.m_name != nullptr && stmt->label != nullptr && + (strcmp(labelDesc.m_name, stmt->label) == 0)) { + JIT_GOTO(labelDesc.m_postBlock); + return true; + } + ++itr; + } + } + MOT_LOG_TRACE("Failed to find label %s in EXIT/CONTINUE statment", stmt->label); + return false; + } +} + +static bool ProcessStatementExit(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_exit* stmt) +{ + MOT_LOG_TRACE("Processing statement: exit/continue"); + Oid resultType = 0; + if (stmt->is_exit) { + JIT_DEBUG_LOG(stmt->cond ? "EXIT conditional" : "EXIT unconditional"); + } else { + JIT_DEBUG_LOG(stmt->cond ? "CONTINUE conditional" : "CONTINUE unconditional"); + } + + if (!stmt->cond) { + return ProcessStatementExitGoto(ctx, stmt); + } + + bool result = true; + JIT_IF_BEGIN(stmt_exit); + llvm::Value* condValue = ProcessExpr(ctx, stmt->cond, &resultType); + if (condValue == nullptr) { + MOT_LOG_TRACE("Failed to process exit/continue statement: Failed to process lower bound expression"); + result = false; + } + if (result) { + JIT_IF_EVAL(condValue); + { + result = ProcessStatementExitGoto(ctx, stmt); + } + } + JIT_IF_END(); + return result; +} + +static bool ProcessRetVarNo(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return* stmt) +{ + // if the variable is scalar then just put in slot 0 + bool result = false; + int varNo = stmt->retvarno; + PLpgSQL_datum* datum = ctx->_compiled_function->datums[varNo]; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + llvm::Value* value = GetLocalOrParamByVarNo(ctx, varNo); + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(value, true); + value = GetIsNullLocalOrParamByVarNo(ctx, varNo); + llvm::Value* valueIsNull = ctx->m_builder->CreateLoad(value, true); + llvm::Value* valueIsNullInt = ctx->m_builder->CreateCast(llvm::Instruction::SExt, valueIsNull, ctx->INT32_T); +#ifdef MOT_JIT_DEBUG + PLpgSQL_var* varDatum = (PLpgSQL_var*)datum; + JIT_DEBUG_LOG_DATUM("Setting slot value", loadedValue, valueIsNull, varDatum->datatype->typoid); +#endif + AddSetSlotValue(ctx, 0, loadedValue, valueIsNullInt); + result = true; + } else if (datum->dtype == PLPGSQL_DTYPE_ROW) { + PLpgSQL_row* row = (PLpgSQL_row*)datum; + if (ctx->m_resultTupDesc == nullptr) { + MemoryContext oldCtx = CurrentMemoryContext; + CurrentMemoryContext = INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR); + result = GetFuncTypeClass(ctx->m_plan, &ctx->m_resultTupDesc, &ctx->m_typeFuncClass); + if (result) { + ctx->m_rowTupDesc = PrepareTupleDescFromRow(row, ctx->_compiled_function); + } + CurrentMemoryContext = oldCtx; + if (!result) { + return false; + } + // the result tuple desc is moved/cloned into the JitSource/Context + } + JIT_IF_BEGIN(composite_result); + llvm::Value* isCompositeResult = AddIsCompositeResult(ctx); + JIT_IF_EVAL(isCompositeResult); + { + llvm::Value* resultDatums = AddCreateResultDatums(ctx); + llvm::Value* resultNulls = AddCreateResultNulls(ctx); + int columnId = 0; + for (int i = 0; i < row->nfields; ++i) { + int fieldVarNo = row->varnos[i]; + if (fieldVarNo >= 0) { // skip dropped columns + JIT_DEBUG_LOG_INT("Returning out parameter with varno: %d", fieldVarNo); + JIT_DEBUG_LOG_VARNO(fieldVarNo); + llvm::Value* value = GetLocalOrParamByVarNo(ctx, fieldVarNo); + llvm::Value* resultVar = ctx->m_builder->CreateLoad(value, true); + value = GetIsNullLocalOrParamByVarNo(ctx, fieldVarNo); + llvm::Value* resultIsNull = ctx->m_builder->CreateLoad(value, true); + llvm::Value* resultIsNullInt = + ctx->m_builder->CreateCast(llvm::Instruction::SExt, resultIsNull, ctx->INT32_T); +#ifdef MOT_JIT_DEBUG + PLpgSQL_datum* fieldDatum = ctx->_compiled_function->datums[fieldVarNo]; + MOT_ASSERT(fieldDatum->dtype == PLPGSQL_DTYPE_VAR); + if (fieldDatum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* varDatum = (PLpgSQL_var*)fieldDatum; + JIT_DEBUG_LOG_DATUM( + "Setting result value", resultVar, resultIsNull, varDatum->datatype->typoid); + } else { + JIT_DEBUG_LOG("Setting non-scalar result value"); + } +#endif + AddSetResultValue(ctx, resultDatums, resultNulls, columnId++, resultVar, resultIsNullInt); + } + } + llvm::Value* resultHeapTuple = AddCreateResultHeapTuple(ctx, resultDatums, resultNulls); + AddSetSlotValue(ctx, 0, resultHeapTuple, JIT_CONST_INT32(0)); + } + JIT_ELSE(); + { + int columnId = 0; + for (int i = 0; i < row->nfields; ++i) { + int fieldVarNo = row->varnos[i]; + if (fieldVarNo >= 0) { // skip dropped columns + JIT_DEBUG_LOG_INT("Returning out parameter with varno: %d", fieldVarNo); + JIT_DEBUG_LOG_VARNO(fieldVarNo); + llvm::Value* value = GetLocalOrParamByVarNo(ctx, fieldVarNo); + llvm::Value* resVar = ctx->m_builder->CreateLoad(value, true); + value = GetIsNullLocalOrParamByVarNo(ctx, fieldVarNo); + llvm::Value* resIsNull = ctx->m_builder->CreateLoad(value, true); + llvm::Value* resIsNullInt = + ctx->m_builder->CreateCast(llvm::Instruction::SExt, resIsNull, ctx->INT32_T); +#ifdef MOT_JIT_DEBUG + PLpgSQL_datum* fieldDatum = ctx->_compiled_function->datums[fieldVarNo]; + MOT_ASSERT(fieldDatum->dtype == PLPGSQL_DTYPE_VAR); + if (fieldDatum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* varDatum = (PLpgSQL_var*)fieldDatum; + JIT_DEBUG_LOG_DATUM("Setting slot value", resVar, resIsNull, varDatum->datatype->typoid); + } else { + JIT_DEBUG_LOG("Setting non-scalar slot value"); + } +#endif + AddSetSlotValue(ctx, columnId++, resVar, resIsNullInt); + } + } + } + JIT_IF_END(); + result = true; + } else { + MOT_LOG_TRACE("Unsupported return type %d", datum->dtype); + } + + if (result) { + // store the tuple and communicate back one tuple was processed. + AddExecStoreVirtualTuple(ctx); + AddSetTpProcessed(ctx, JIT_CONST_INT64(1)); + AddSetScanEnded(ctx, JIT_CONST_INT32(1)); + + // commit all open sub-transactions, and we are done + AddCleanupBeforeReturn(ctx, ctx->m_initSubXid); + CheckThrownException(ctx); + AddLlvmClearExceptionStack(ctx); + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_TOTAL, false); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_OK)); // success-done + } + return result; +} + +static bool ProcessStatementReturn(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return* stmt) +{ + // we disregard the retvarno member, as we return values through the tuple slot + MOT_LOG_TRACE("Processing statement: return"); + + // sometimes we return a variable by no and expression is null + if (stmt->retvarno >= 0) { + MOT_LOG_TRACE("Process return var no %d", stmt->retvarno); + return ProcessRetVarNo(ctx, stmt); + } + + // we always expect a simple expression here + MOT_LOG_TRACE("Process return expression"); + Oid resultType = 0; + if (stmt->expr != nullptr) { // could return void + llvm::Value* resultValue = ProcessExpr(ctx, stmt->expr, &resultType); + if (resultValue == nullptr) { + MOT_LOG_TRACE("Failed to process return statement: Failed to process result value expression"); + return false; + } + + // check if expression evaluated to null (this is a top level expression at position 0) + llvm::Value* isNull = AddGetExprIsNull(ctx); + + // store the result value in the result tuple slot (always tuple column 0 for single return value) + AddSetSlotValue(ctx, 0, resultValue, isNull); + AddExecStoreVirtualTuple(ctx); + } + + // communicate back one tuple was processed. commit all open sub-transactions, and we are done + AddSetTpProcessed(ctx, JIT_CONST_INT64(1)); + AddSetScanEnded(ctx, JIT_CONST_INT32(1)); + AddCleanupBeforeReturn(ctx, ctx->m_initSubXid); + CheckThrownException(ctx); + AddLlvmClearExceptionStack(ctx); + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_TOTAL, false); + JIT_RETURN(JIT_CONST_INT32(MOT::RC_OK)); // success-done + + return true; +} + +static bool ProcessStatementReturnNext(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return_next* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: return next"); + return false; +} + +static bool ProcessStatementReturnQuery(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_return_query* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: return query"); + return false; +} + +static bool IsSimpleExprListStatic(JitLlvmFunctionCodeGenContext* ctx, List* exprList) +{ + ListCell* lc = nullptr; + foreach (lc, exprList) { + Expr* expr = (Expr*)lfirst(lc); + if (!IsSimpleExprStatic(ctx, expr)) { + return false; + } + } + return true; +} + +static bool IsSimpleExprStatic(JitLlvmFunctionCodeGenContext* ctx, Expr* expr) +{ + // currently only direct constants are considered as static (i.e. can be evaluated during compile-time) + // and functions over constants + switch (expr->type) { + case T_Const: + return true; + + case T_RelabelType: + return IsSimpleExprStatic(ctx, ((RelabelType*)expr)->arg); + + case T_FuncExpr: + return IsSimpleExprListStatic(ctx, ((FuncExpr*)expr)->args); + + case T_OpExpr: + return IsSimpleExprListStatic(ctx, ((OpExpr*)expr)->args); + + case T_BoolExpr: + return IsSimpleExprListStatic(ctx, ((BoolExpr*)expr)->args); + + default: + return false; + } +} + +static bool ProcessStatementRaise(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_raise* stmt) +{ + MOT_LOG_TRACE("Processing statement: raise"); + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + if ((stmt->elog_level == DEBUG1) && ((strcmp(stmt->message, MOT_JIT_PROFILE_BEGIN_MESSAGE) == 0) || + (strcmp(stmt->message, MOT_JIT_PROFILE_END_MESSAGE) == 0))) { + if (stmt->options != nullptr) { + PLpgSQL_raise_option* option = (PLpgSQL_raise_option*)linitial(stmt->options); + if ((option->opt_type == PLPGSQL_RAISEOPTION_HINT) && (option->expr->query != nullptr)) { + int beginRegion = (strcmp(stmt->message, MOT_JIT_PROFILE_BEGIN_MESSAGE) == 0) ? 1 : 0; + InjectProfileData(ctx, option->expr->query, beginRegion); + return true; + } + } + } + } + // no other implementation yet + return false; + + // special case: empty RAISE instruction emits a re-throw + if (stmt->condname == nullptr && stmt->message == nullptr && stmt->options == NIL) { + // make sure we are safe inside a try-catch block + if (!JIT_TRY_CURRENT()) { + MOT_LOG_TRACE("Cannot use re-throw RAISE not inside exception block"); + return false; + } + JIT_RETHROW(); + return true; + } +} + +inline void HandleTooManyRows(JitLlvmFunctionCodeGenContext* ctx, int tcount) +{ + MOT::mot_string errMsg; + if (errMsg.format("query returned %d rows more than one row", tcount)) { + AddSetSqlStateAndErrorMessage(ctx, errMsg.c_str(), ERRCODE_TOO_MANY_ROWS); + } else { + MOT_LOG_TRACE("Failed to format error message for handling too many rows on SP query return value processing"); + AddSetSqlStateAndErrorMessage(ctx, "query returned more than one row", ERRCODE_TOO_MANY_ROWS); + } + JIT_DEBUG_LOG("JIT SP exception: Too many rows found for INTO clause with STRICT execution"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_TOO_MANY_ROWS)); +} + +static void ProcessSubQueryResult(JitLlvmFunctionCodeGenContext* ctx, int subQueryId, llvm::Value* queryResult, + llvm::Value* tuplesProcessed, bool strict, bool mod, int tcount, bool into, bool isPerform) +{ + // prepare FOUND magic variable + if (ctx->m_usingMagicVars) { + llvm::Value* foundValue = GetLocalOrParamByVarNo(ctx, ctx->_compiled_function->found_varno); + llvm::Value* foundIsNull = GetIsNullLocalOrParamByVarNo(ctx, ctx->_compiled_function->found_varno); + llvm::Value* sqlNotFoundValue = GetLocalOrParamByVarNo(ctx, ctx->_compiled_function->sql_notfound_varno); + llvm::Value* sqlNotFoundIsNull = GetIsNullLocalOrParamByVarNo(ctx, ctx->_compiled_function->sql_notfound_varno); + llvm::Value* sqlRowCountValue = GetLocalOrParamByVarNo(ctx, ctx->_compiled_function->sql_rowcount_varno); + llvm::Value* sqlRowCountIsNull = GetIsNullLocalOrParamByVarNo(ctx, ctx->_compiled_function->sql_rowcount_varno); + llvm::Value* trueDatum = llvm::ConstantInt::get(ctx->INT64_T, BoolGetDatum(true), false); + llvm::Value* falseDatum = llvm::ConstantInt::get(ctx->INT64_T, BoolGetDatum(false), false); + llvm::Value* falseBool = JIT_CONST_BOOL(false); + + // update magic variables according to sub-query result (make sure not null) + ctx->m_builder->CreateStore(falseBool, foundIsNull, true); + ctx->m_builder->CreateStore(falseBool, sqlNotFoundIsNull, true); + ctx->m_builder->CreateStore(falseBool, sqlRowCountIsNull, true); + + JIT_DEBUG_LOG("Setting magic variables"); + llvm::Value* spiResult = AddGetSpiResult(ctx, subQueryId); + JIT_SWITCH_BEGIN(spi_result, spiResult); + { + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_SELECT)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_INSERT)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_UPDATE)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_DELETE)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_INSERT_RETURNING)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_UPDATE_RETURNING)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_DELETE_RETURNING)); + JIT_CASE_MANY(JIT_CONST_INT32(SPI_OK_MERGE)); + JIT_CASE_MANY_TERM(); + { + JIT_IF_BEGIN(set_magic_vars); + JIT_IF_EVAL(tuplesProcessed); + { + JIT_DEBUG_LOG("Setting FOUND magic variables to TRUE"); + ctx->m_builder->CreateStore(trueDatum, foundValue, true); + ctx->m_builder->CreateStore(falseDatum, sqlNotFoundValue, true); + } + JIT_ELSE(); + { + JIT_DEBUG_LOG("Setting FOUND magic variables to FALSE"); + ctx->m_builder->CreateStore(falseDatum, foundValue, true); + ctx->m_builder->CreateStore(trueDatum, sqlNotFoundValue, true); + } + JIT_IF_END(); + + // set the row count + llvm::Value* tuplesProcessed64 = + ctx->m_builder->CreateCast(llvm::Instruction::SExt, tuplesProcessed, ctx->INT64_T); + ctx->m_builder->CreateStore(tuplesProcessed64, sqlRowCountValue, true); + JIT_CASE_BREAK(); + } + JIT_CASE_END(); + + JIT_CASE(JIT_CONST_INT32(SPI_OK_REWRITTEN)); + { + JIT_DEBUG_LOG("Setting only FOUND magic variable to FALSE when return value is SPI_OK_REWRITTEN"); + ctx->m_builder->CreateStore(falseDatum, foundValue, true); + JIT_CASE_BREAK(); + } + JIT_CASE_END(); + + JIT_CASE_DEFAULT(); + { + // this is unexpected (should be handled in helpers), but we deal with it anyway + AddSetSqlStateAndErrorMessage( + ctx, "SPI_execute_plan_with_paramlist failed executing query", ERRCODE_FEATURE_NOT_SUPPORTED); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_FEATURE_NOT_SUPPORTED)); + JIT_CASE_BREAK(); + } + JIT_CASE_END(); + } + JIT_SWITCH_END(); + } + + // following checks are NOT generated for PERFORM statements + if (!isPerform) { + if (into) { + if (strict) { + JIT_IF_BEGIN(strict_res_empty_check); + JIT_IF_EVAL_NOT(tuplesProcessed); + { + AddSetSqlStateAndErrorMessage( + ctx, "query returned no rows when process INTO", ERRCODE_NO_DATA_FOUND); + JIT_DEBUG_LOG("JIT SP exception: No rows found for INTO clause with STRICT execution"); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_NO_DATA_FOUND)); + } + JIT_IF_END(); + } + if (strict || mod) { + JIT_IF_BEGIN(strict_res_too_many_check); + JIT_IF_EVAL_CMP(tuplesProcessed, JIT_CONST_INT32(1), JitExec::JIT_ICMP_GT); + { + HandleTooManyRows(ctx, tcount); + } + JIT_IF_END(); + } + } else { + // we check number of tuples only for select sub-queries (be careful with non-jittable query) + JitCallSitePlan* callSitePlan = &ctx->m_plan->m_callSitePlanList[subQueryId]; + if ((callSitePlan->m_queryPlan && IsSelectCommand(callSitePlan->m_queryPlan->_command_type)) || + (!callSitePlan->m_queryPlan && (callSitePlan->m_queryCmdType == CMD_SELECT))) { + JIT_IF_BEGIN(res_no_into_check); + JIT_IF_EVAL_CMP(tuplesProcessed, JIT_CONST_INT32(0), JitExec::JIT_ICMP_GT); + { + HandleTooManyRows(ctx, tcount); + } + JIT_IF_END(); + } + } + } +} + +static void CheckThrownException(JitLlvmFunctionCodeGenContext* ctx) +{ + JIT_IF_BEGIN(check_exception); + + llvm::Value* exceptionStatus = JIT_EXCEPTION_STATUS(); + JIT_IF_EVAL(exceptionStatus); + { + // just re-throw - catch handler will load error info + JIT_RETHROW(); + } + JIT_IF_END(); +} + +static bool UpdateTCount(SPIPlanPtr spiPlan, int& tcount) +{ + volatile bool result = false; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + CachedPlan* cplan = SPI_plan_get_cached_plan(spiPlan); + if (cplan == nullptr) { + MOT_LOG_TRACE("Cannot get cached plan from SPI plan"); + } else { + Node* stmt = (Node*)linitial(cplan->stmt_list); + bool canSetTag = false; + if (IsA(stmt, PlannedStmt)) { + canSetTag = ((PlannedStmt*)stmt)->canSetTag; + } + if (!canSetTag) { + tcount = 0; // run to completion + } + ReleaseCachedPlan(cplan, spiPlan->saved); + result = true; + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while getting cached plan: %s", edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + return (bool)result; +} + +static llvm::Value* ExecSubQuery(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_expr* expr, int tcount, bool strict, + bool mod, bool into, int* subQueryId, bool isPerform /* = false */) +{ + // find sub-query id by query string + char* queryString = expr->query; + *subQueryId = -1; + for (int i = 0; i < ctx->m_plan->_query_count; ++i) { + JitCallSitePlan* callSitePlan = &ctx->m_plan->m_callSitePlanList[i]; + if (strcmp(queryString, callSitePlan->m_queryString) == 0) { + *subQueryId = i; + break; + } + } + if (*subQueryId == -1) { + MOT_LOG_TRACE("execSubQuery(): Could not find sub-query: %s", queryString); + return nullptr; + } + MOT_LOG_TRACE("Found sub-query %d: %s", *subQueryId, queryString); + + JitCallSitePlan* callSitePlan = &ctx->m_plan->m_callSitePlanList[*subQueryId]; + if (into && (callSitePlan->m_queryCmdType != CMD_SELECT)) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, + "JIT Compile", + "INTO used with a command that cannot return data, on sub-query %d: %s", + *subQueryId, + callSitePlan->m_queryString); + return nullptr; + } else if (!into && !isPerform && (callSitePlan->m_queryCmdType == CMD_SELECT)) { + // unless this is invoke into procedure, this is unacceptable + if (((callSitePlan->m_queryPlan != nullptr) && (callSitePlan->m_queryPlan->_plan_type != JIT_PLAN_INVOKE)) || + ((callSitePlan->m_queryPlan == nullptr) && (callSitePlan->m_isUnjittableInvoke))) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, + "JIT Compile", + "query has no destination for result data, on sub-query %d: %s", + *subQueryId, + callSitePlan->m_queryString); + return nullptr; + } + } + JIT_DEBUG_LOG_INT("Passing %d parameters to sub query", callSitePlan->m_callParamCount); + for (int i = 0; i < callSitePlan->m_callParamCount; ++i) { + JitCallParamInfo* param = &callSitePlan->m_callParamInfo[i]; + if (param->m_paramKind == JitCallParamKind::JIT_CALL_PARAM_ARG) { + // pass parameter by ref (already handled directly by ExecuteSubQuery) + MOT_LOG_TRACE("Passing parameter %d to from argument %d to sub-query %d by reference (SP argument)", + param->m_paramIndex, + param->m_invokeArgIndex, + *subQueryId); + JIT_DEBUG_LOG_INT("Passing argument at pos %d to sub query by reference", param->m_invokeArgIndex); + } else { + // pass parameter by value + int datumIndex = param->m_invokeDatumIndex; + MOT_LOG_TRACE("Passing parameter %d from datum %d to sub-query %d by value (SP local variable)", + param->m_paramIndex, + datumIndex, + *subQueryId); + llvm::Value* value = GetLocalOrParamByVarNo(ctx, datumIndex); + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(ctx->DATUM_T, value, true); + value = GetIsNullLocalOrParamByVarNo(ctx, datumIndex); + llvm::Value* isNull = ctx->m_builder->CreateLoad(ctx->BOOL_T, value, true); + llvm::Value* isNull32 = ctx->m_builder->CreateCast(llvm::Instruction::CastOps::SExt, isNull, ctx->INT32_T); + AddSetSPSubQueryParamValue( + ctx, *subQueryId, param->m_paramIndex, param->m_paramType, loadedValue, isNull32); + } + } + + // fix tcount - this can throw ereport, so be careful + if (!UpdateTCount(callSitePlan->m_spiPlan, tcount)) { + MOT_LOG_TRACE("Failed to update tcount"); + return nullptr; + } + + // prepare call-site for query in the function context + llvm::Value* queryResult = nullptr; + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_CHILD_CALL, true); + queryResult = AddExecuteSubQuery(ctx, *subQueryId, tcount); + InjectProfileData(ctx, MOT_JIT_PROFILE_REGION_CHILD_CALL, false); + JIT_DEBUG_LOG("Returned from sub-query"); + + // check for invalid SQL state + JIT_IF_BEGIN(check_subq_except); + llvm::Value* sqlState = AddGetSqlState(ctx); + JIT_IF_EVAL(sqlState); + { + // mark exception origin as external and let the catch handler to load error info + AddSetExceptionOrigin(ctx, JIT_EXCEPTION_EXTERNAL); + JIT_THROW(sqlState); + } + JIT_IF_END(); + + // check for other exception (not due to some invalid SQL state) + CheckThrownException(ctx); + + // collect result and process it + llvm::Value* tuplesProcessed = AddGetTuplesProcessed(ctx, *subQueryId); + ProcessSubQueryResult(ctx, *subQueryId, queryResult, tuplesProcessed, strict, mod, tcount, into, isPerform); + return tuplesProcessed; +} + +static bool AssignScalarValue(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_var* target, llvm::Value* assignee, + llvm::Value* assigneeIsNull, llvm::Value* value, llvm::Value* isNull, Oid sourceType) +{ + // first check if null is allowed + if (target->notnull) { + JIT_IF_BEGIN(var_not_null); + JIT_IF_EVAL(isNull); + { + AddSetSqlStateAndErrorMessage( + ctx, "null value cannot be assigned to variable declared NOT NULL", ERRCODE_NULL_VALUE_NOT_ALLOWED); + JIT_THROW(JIT_CONST_UINT32(ERRCODE_NULL_VALUE_NOT_ALLOWED)); + } + JIT_IF_END(); + } + + // collect cast information + Oid targetType = target->datatype->typoid; + int32 typeMod = target->datatype->atttypmod; + Oid funcId = InvalidOid; + CoercionPathType coercePath = COERCION_PATH_NONE; + Oid funcId2 = InvalidOid; + CoercionPathType coercePath2 = COERCION_PATH_NONE; + int nargs = 0; + if ((targetType != sourceType) || (typeMod != -1)) { + MOT_LOG_TRACE("AssignScalarValue(): converting from type %u to type %u", sourceType, targetType); + coercePath = find_coercion_pathway(targetType, sourceType, COERCION_ASSIGNMENT, &funcId); + if ((funcId != InvalidOid) && + !(coercePath == COERCION_PATH_COERCEVIAIO || coercePath == COERCION_PATH_ARRAYCOERCE)) { + MOT_LOG_TRACE("AssignScalarValue(): Found coercion path via func id: %d", funcId); + coercePath2 = find_typmod_coercion_function(targetType, &funcId2); + if (coercePath2 == COERCION_PATH_FUNC && OidIsValid(funcId2)) { + nargs = get_func_nargs(funcId2); + } + } + } + + // call cast function + JIT_IF_BEGIN(assign_null); + JIT_IF_EVAL_NOT(isNull); + { + if ((targetType != sourceType) || (typeMod != -1) || !target->datatype->typbyval) { + llvm::Value* castResult = AddCastValue(ctx, + value, + sourceType, + targetType, + typeMod, + coercePath, + funcId, + coercePath2, + funcId2, + nargs, + target->datatype->typbyval); + CheckThrownException(ctx); + ctx->m_builder->CreateStore(castResult, assignee, true); + } else { + ctx->m_builder->CreateStore(value, assignee, true); + } + } + JIT_IF_END(); + + // store null value + llvm::Value* isNullBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, isNull, ctx->BOOL_T); + ctx->m_builder->CreateStore(isNullBool, assigneeIsNull, true); + +#ifdef MOT_JIT_DEBUG + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(ctx->DATUM_T, assignee, true); + llvm::Value* loadedIsNull = ctx->m_builder->CreateLoad(ctx->BOOL_T, assigneeIsNull, true); + JIT_DEBUG_LOG_DATUM("Stored value", loadedValue, loadedIsNull, targetType); +#endif + + return true; +} + +static bool AssignValue(JitLlvmFunctionCodeGenContext* ctx, int varNo, PLpgSQL_expr* expr) +{ + MOT_LOG_TRACE("Assign into varno %d", varNo); + JIT_DEBUG_LOG_VARNO(varNo); + llvm::Value* assignee = GetLocalOrParamByVarNo(ctx, varNo); + llvm::Value* assigneeIsNull = GetIsNullLocalOrParamByVarNo(ctx, varNo); + if (assignee == nullptr) { + MOT_LOG_TRACE("Failed to assign value: Failed to find local assignee by varno %d", varNo); + return false; + } + + Oid valueType; + llvm::Value* value = ProcessExpr(ctx, expr, &valueType); + if (value == nullptr) { + MOT_LOG_TRACE("Failed to assign value: Failed to process value expression"); + return false; + } + llvm::Value* isNull = AddGetExprIsNull(ctx); + + // code adapted from exec_assign_value() in pl_exec.cpp + PLpgSQL_datum* target = ctx->_compiled_function->datums[varNo]; + if (target->dtype == PLPGSQL_DTYPE_VAR) { + JIT_DEBUG_LOG_INT("Assigning scalar into varno: %d", varNo); + bool result = AssignScalarValue(ctx, (PLpgSQL_var*)target, assignee, assigneeIsNull, value, isNull, valueType); + JIT_DEBUG_LOG_VARNO(varNo); +#ifdef MOT_JIT_DEBUG + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(ctx->DATUM_T, assignee, true); + llvm::Value* loadedIsNull = ctx->m_builder->CreateLoad(ctx->BOOL_T, assigneeIsNull, true); + JIT_DEBUG_LOG_DATUM( + "Stored value after assign 2", loadedValue, loadedIsNull, ((PLpgSQL_var*)target)->datatype->typoid); +#endif + return result; + } else { + MOT_LOG_TRACE("Unsupported assignment"); + return false; + } + + bool failed = false; + JIT_IF_BEGIN(assign_null); + JIT_IF_EVAL_NOT(isNull); + { + llvm::StoreInst* storeInst = ctx->m_builder->CreateStore(value, assignee, true); + if (!storeInst) { + MOT_LOG_TRACE("Failed to create store instruction"); + failed = true; + } + } + JIT_IF_END(); + if (failed) { + return false; + } + + llvm::Value* isNullBool = ctx->m_builder->CreateCast(llvm::Instruction::Trunc, isNull, ctx->BOOL_T); + llvm::StoreInst* storeInst = ctx->m_builder->CreateStore(isNullBool, assigneeIsNull, true); + if (!storeInst) { + MOT_LOG_TRACE("Failed to create store instruction"); + return false; + } + + return true; +} + +static bool IsSubQueryResultComposite(JitLlvmFunctionCodeGenContext* ctx, int subQueryId) +{ + bool result = false; + if (subQueryId < ctx->m_plan->_query_count) { + JitCallSitePlan* callSitePlan = &ctx->m_plan->m_callSitePlanList[subQueryId]; + if ((callSitePlan->m_tupDesc->natts == 1) && (callSitePlan->m_tupDesc->attrs[0]->atttypid == RECORDOID)) { + result = true; + } + } + MOT_LOG_DEBUG("Sub-query %d is composite: %s", subQueryId, result ? "true" : "false"); + return result; +} + +static void DetermineStrictFlagAndTCount(PLpgSQL_stmt_execsql* stmt, bool& strict, int& tcount) +{ + strict = stmt->strict; + if (stmt->into) { + if (!stmt->mod_stmt) { + strict = true; + } + if (strict || stmt->mod_stmt) { + tcount = 2; + } else { + tcount = 1; + } + } +} + +static bool ProcessStatementExecSql(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_execsql* stmt) +{ + // determine the number of invocation of the query + int tcount = 0; + bool strict = stmt->strict; + DetermineStrictFlagAndTCount(stmt, strict, tcount); + + MOT_LOG_TRACE("Processing statement: exec-sql"); + int subQueryId = -1; + llvm::Value* queryResult = + ExecSubQuery(ctx, stmt->sqlstmt, tcount, strict, stmt->mod_stmt, stmt->into, &subQueryId); + if ((queryResult == nullptr) || (subQueryId < 0)) { + MOT_LOG_TRACE("processStatementExecSql(): Failed to process sub-query"); + return false; + } + + if (stmt->into) { + // Determine if we assign to a record or a row + PLpgSQL_rec* rec = nullptr; + PLpgSQL_row* row = nullptr; + if (stmt->rec != nullptr) { + rec = (PLpgSQL_rec*)(ctx->_compiled_function->datums[stmt->rec->dno]); + } else if (stmt->row != nullptr) { + row = (PLpgSQL_row*)(ctx->_compiled_function->datums[stmt->row->dno]); + } else { + MOT_LOG_TRACE("unsupported target, use record and row instead."); + return false; + } + + bool failed = false; + JIT_IF_BEGIN(into_null); + JIT_IF_EVAL_NOT(queryResult); + { + // set the target to NULL(s) + for (int i = 0; i < stmt->row->nfields; ++i) { + int varNo = stmt->row->varnos[i]; + if (varNo >= 0) { // skip dropped columns + llvm::Value* resultVar = GetLocalOrParamByVarNo(ctx, varNo); + llvm::Value* resultIsNullVar = GetIsNullLocalOrParamByVarNo(ctx, varNo); + llvm::StoreInst* storeInst = ctx->m_builder->CreateStore(JIT_CONST_UINT64(0), resultVar, true); + if (!storeInst) { + MOT_LOG_TRACE("Failed to store zero in datum value"); + failed = true; + break; + } + storeInst = ctx->m_builder->CreateStore(JIT_CONST_BOOL(true), resultIsNullVar, true); + if (!storeInst) { + MOT_LOG_TRACE("Failed to store non-zero value in datum is-null property"); + failed = true; + break; + } + } + } + } + JIT_ELSE(); + { + // Put the result row into the target variables + bool subQueryResultComposite = IsSubQueryResultComposite(ctx, subQueryId); + llvm::Value* resultHeapTuple = nullptr; + llvm::Value* isNullRef = nullptr; + if (subQueryResultComposite) { + resultHeapTuple = AddGetSubQueryResultHeapTuple(ctx, subQueryId); + isNullRef = ctx->m_builder->CreateAlloca(ctx->INT32_T); + } + llvm::Value* slotValue = nullptr; + llvm::Value* isNullValue = nullptr; + for (int i = 0; i < stmt->row->nfields; ++i) { + int varNo = stmt->row->varnos[i]; + if (varNo >= 0) { // skip dropped columns + if (subQueryResultComposite) { + slotValue = AddGetHeapTupleValue(ctx, resultHeapTuple, subQueryId, i, isNullRef); + isNullValue = ctx->m_builder->CreateLoad(isNullRef); + } else { + slotValue = AddGetSubQuerySlotValue(ctx, subQueryId, i); + isNullValue = AddGetSubQuerySlotIsNull(ctx, subQueryId, i); + } + PLpgSQL_datum* target = ctx->_compiled_function->datums[varNo]; + + llvm::Value* resultVar = GetLocalOrParamByVarNo(ctx, varNo); + llvm::Value* resultIsNullVar = GetIsNullLocalOrParamByVarNo(ctx, varNo); + + JIT_DEBUG_LOG_INT("Assigning scalar from sub-query into varno: %d", varNo); + PLpgSQL_datum* datum = ctx->_compiled_function->datums[varNo]; + if (datum->dtype != PLPGSQL_DTYPE_VAR) { + MOT_LOG_TRACE("Cannot assign non-var row field at var-no: %d", varNo); + failed = true; + break; + } + JitCallSitePlan* callSitePlan = &ctx->m_plan->m_callSitePlanList[subQueryId]; + Oid slotType = callSitePlan->m_tupDesc->attrs[i]->atttypid; + if (!AssignScalarValue( + ctx, (PLpgSQL_var*)target, resultVar, resultIsNullVar, slotValue, isNullValue, slotType)) { + MOT_LOG_TRACE("Failed to assign scalar value form sub-query"); + failed = true; + break; + } + JIT_DEBUG_LOG_VARNO(varNo); +#ifdef MOT_JIT_DEBUG + llvm::Value* loadedValue = ctx->m_builder->CreateLoad(ctx->DATUM_T, resultVar, true); + llvm::Value* loadedIsNull = ctx->m_builder->CreateLoad(ctx->BOOL_T, resultIsNullVar, true); + JIT_DEBUG_LOG_DATUM( + "Stored value 2", loadedValue, loadedIsNull, ((PLpgSQL_var*)target)->datatype->typoid); +#endif + } + } + } + JIT_IF_END(); + if (failed) { + return false; + } + } + + // cleanup non-jittable sub-query resources (SPI tub-table can be released now after datum values copied to result) + AddReleaseNonJitSubQueryResources(ctx, subQueryId); + + return true; +} + +static bool ProcessStatementPerform(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_perform* stmt) +{ + MOT_LOG_TRACE("Processing statement: perform"); + int subQueryId = -1; + llvm::Value* queryResult = ExecSubQuery(ctx, stmt->expr, 0, false, false, false, &subQueryId, true); + if ((queryResult == nullptr) || (subQueryId < 0)) { + MOT_LOG_TRACE("ProcessStatementPerform(): Failed to process sub-query"); + return false; + } + return true; +} + +static bool ProcessStatementDynExecute(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_dynexecute* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: dyn-exec-sql"); + return false; +} + +static bool ProcessStatementDynForS(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_dynfors* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: dyn-fors"); + return false; +} + +static bool ProcessStatementGetDiag(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_getdiag* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: get-diag"); + return false; +} + +static bool ProcessStatementOpen(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_open* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: open"); + return false; +} + +static bool ProcessStatementFetch(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_fetch* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: fetch"); + return false; +} + +static bool ProcessStatementClose(JitLlvmFunctionCodeGenContext* ctx, PLpgSQL_stmt_close* stmt) +{ + // Not supported + MOT_LOG_TRACE("Processing statement: close"); + return false; +} + +static bool DefineLocalVariables(JitLlvmFunctionCodeGenContext* ctx) +{ + for (int i = 0; i < ctx->_compiled_function->ndatums; ++i) { + if (i == ctx->_compiled_function->sql_bulk_exceptions_varno) { + // This datum is used only for SAVE EXCEPTIONS and SQL%BULK_EXCEPTION, which we don't support. + continue; + } + + if (ctx->m_datums[i] == nullptr) { // skip already defined function arguments + if (!DefineBlockLocalVar(ctx, i)) { + MOT_LOG_TRACE("Failed to define local variable %d", i); + return false; + } + } + } + + return true; +} + +static bool InitLocalVariables(JitLlvmFunctionCodeGenContext* ctx) +{ + for (int i = 0; i < ctx->_compiled_function->ndatums; ++i) { + if (i == ctx->_compiled_function->sql_bulk_exceptions_varno) { + // This datum is used only for SAVE EXCEPTIONS and SQL%BULK_EXCEPTION, which we don't support. + continue; + } + + if (ctx->m_datums[i] != nullptr) { + if (!InitBlockLocalVar(ctx, i)) { + MOT_LOG_TRACE("Failed to initialize local variable %d", i); + return false; + } + } + } + + return true; +} + +#define JIT_ASSERT_SP_CODEGEN_UTIL_VALID() \ + do { \ + MOT_ASSERT(JIT_IF_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_WHILE_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_DO_WHILE_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_FOR_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_SWITCH_CURRENT() == nullptr); \ + MOT_ASSERT(JIT_TRY_CURRENT() == nullptr); \ + } while (0) + +extern MotJitContext* JitCodegenLlvmFunction(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, + ReturnSetInfo* returnSetInfo, JitPlan* plan, JitCodegenStats& codegenStats) +{ + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + bool procNameIsNull = false; + bool procSrcIsNull = false; + bool procIsStrictIsNull = false; + Datum procNameDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proname, &procNameIsNull); + Datum procSrcDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_prosrc, &procSrcIsNull); + Datum procIsStrictDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proisstrict, &procIsStrictIsNull); + if (procNameIsNull || procSrcIsNull || procIsStrictIsNull) { + MOT_LOG_TRACE("Failed to generate jitted code for stored procedure: catalog entry for stored procedure " + "contains null attributes"); + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + return nullptr; + } + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile char* functionName = NameStr(*DatumGetName(procNameDatum)); + MOT_LOG_TRACE("Generating LLVM-jitted code for stored procedure %s with %d args at thread %p", + functionName, + function->fn_nargs, + (void*)pthread_self()); + + // get required function attributes + char* functionSource = TextDatumGetCString(procSrcDatum); + if (!functionSource) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Parse Stored Procedure", "Failed to allocate memory for stored procedure source code"); + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + return nullptr; + } + bool isStrict = DatumGetBool(procIsStrictDatum); + + // connect to SPI for query parsing/analysis + // note: usually we should already be connected to the SPI manager (since we are called from plpgsql_validator()) + + // prepare compile context + GsCodeGen* codeGen = SetupCodegenEnv(); + if (codeGen == nullptr) { + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + return nullptr; + } + GsCodeGen::LlvmBuilder builder(codeGen->context()); + volatile JitLlvmFunctionCodeGenContext cgCtx = {}; + if (!InitLlvmFunctionCodeGenContext( + (JitLlvmFunctionCodeGenContext*)&cgCtx, codeGen, &builder, function, returnSetInfo)) { + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + FreeGsCodeGen(codeGen); + return nullptr; + } + cgCtx.m_plan = (JitFunctionPlan*)plan; + volatile JitLlvmFunctionCodeGenContext* ctx = &cgCtx; + ctx->m_resultTupDesc = nullptr; + ctx->m_rowTupDesc = nullptr; + + // we temporarily cause the function to be treated as triggered, so we don't need execution state for parsing + volatile bool preParseTrig = function->pre_parse_trig; + function->pre_parse_trig = true; + volatile MotJitContext* jitContext = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + do { // instead of goto + // prepare the jitted function (declare, get arguments into context and define locals) + std::string jittedFunctionName = std::string("MotJittedSp") + "_" + (char*)functionName; + if (!CreateJittedFunction( + (JitLlvmFunctionCodeGenContext*)ctx, jittedFunctionName.c_str(), functionSource, isStrict)) { + break; + } + JIT_DEBUG_LOG("Starting execution of jitted stored procedure"); + + InjectProfileData((JitLlvmFunctionCodeGenContext*)ctx, MOT_JIT_PROFILE_REGION_INIT_VARS, true); + + if (!DefineLocalVariables((JitLlvmFunctionCodeGenContext*)ctx)) { + MOT_LOG_TRACE("Failed to generate jitted code for stored procedure: failed to define local variables"); + break; + } + + bool failed = false; + JIT_TRY_BEGIN(top_init_vars) + { + if (!InitLocalVariables((JitLlvmFunctionCodeGenContext*)ctx)) { + MOT_LOG_TRACE( + "Failed to generate jitted code for stored procedure: failed to initialize local variables"); + failed = true; + } + } + if (!failed) { + JIT_CATCH_ALL(); + { + // cleanup run-time exception stack and report error to user + JIT_DEBUG_LOG("Uncaught exception in top init vars block"); + AddLlvmClearExceptionStack((JitLlvmFunctionCodeGenContext*)ctx); + + // inject profile data before exiting + InjectProfileData((JitLlvmFunctionCodeGenContext*)ctx, MOT_JIT_PROFILE_REGION_INIT_VARS, false); + InjectProfileData((JitLlvmFunctionCodeGenContext*)ctx, MOT_JIT_PROFILE_REGION_TOTAL, false); + + // since this might be a normal flow of events for sub-SP, we return a special return code to avoid + // error reporting in such case + JIT_RETURN(JIT_CONST_INT32(MOT::RC_JIT_SP_EXCEPTION)); + } + JIT_END_CATCH(); + } + JIT_TRY_END() + if (failed) { + break; + } + + InjectProfileData((JitLlvmFunctionCodeGenContext*)ctx, MOT_JIT_PROFILE_REGION_INIT_VARS, false); + + // clear result slot + AddExecClearTuple((JitLlvmFunctionCodeGenContext*)ctx); + + // process main statement block of the function + if (!ProcessFunctionAction((JitLlvmFunctionCodeGenContext*)ctx, function)) { + MOT_LOG_TRACE( + "Failed to generate jitted code for stored procedure: failed to process main statement block"); + break; + } + + // wrap up + jitContext = FinalizeCodegen( + (JitLlvmFunctionCodeGenContext*)ctx, (JitFunctionPlan*)plan, (const char*)functionName, codegenStats); + } while (0); + } + PG_CATCH(); + { + // cleanup + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while generating LLVM code for stored procedure '%s': %s", functionName, edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + // cleanup + function->pre_parse_trig = preParseTrig; + DestroyLlvmFunctionCodeGenContext((JitLlvmFunctionCodeGenContext*)ctx); + JIT_ASSERT_SP_CODEGEN_UTIL_VALID(); + return (MotJitContext*)jitContext; +} + +extern int JitExecLlvmFunction(JitFunctionContext* jitContext, ParamListInfo params, TupleTableSlot* slot, + uint64_t* tuplesProcessed, int* scanEnded) +{ + MOT_ASSERT(jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitFunctionExecState* execState = (JitFunctionExecState*)jitContext->m_execState; + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile int result = 0; + SubTransactionId subXid = ::GetCurrentSubTransactionId(); + + MOT_LOG_TRACE("Calling sigsetjmp on faultBuf %p, function: %s", execState->m_faultBuf, jitContext->m_queryString); + + // we setup a jump buffer in the execution state for fault handling + if (sigsetjmp(execState->m_faultBuf, 1) == 0) { + // execute the jitted-function + result = jitContext->m_llvmSPFunction(params, slot, tuplesProcessed, scanEnded); + } else { + // invoke the global fault handler if there is any registered + uint64_t faultCode = execState->m_exceptionValue; + bytea* errorMessage = DatumGetByteaP(execState->m_errorMessage); + bytea* sqlState = DatumGetByteaP(execState->m_sqlStateString); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Execute JIT", + "Encountered run-time fault %" PRIu64 " (%s), error: %.*s, SQL state: %.*s, while executing %s", + faultCode, + llvm_util::LlvmRuntimeFaultToString(faultCode), + VARSIZE(errorMessage) - VARHDRSZ, + (char*)VARDATA(errorMessage), + VARSIZE(sqlState) - VARHDRSZ, + (char*)VARDATA(sqlState), + jitContext->m_queryString); + + JitReleaseAllSubTransactions(true, subXid); + + // report error to user only for top level context + if (jitContext->m_parentContext && !IsJitSubContextInline(jitContext->m_parentContext)) { + const char* errorDetail = nullptr; + const char* errorHint = nullptr; + bytea* txt = DatumGetByteaP(execState->m_errorMessage); + const char* errorMessage = (const char*)VARDATA(txt); + if (execState->m_errorDetail != 0) { + bytea* txtd = DatumGetByteaP(execState->m_errorDetail); + errorDetail = (const char*)VARDATA(txtd); + } + if (execState->m_errorHint != 0) { + bytea* txth = DatumGetByteaP(execState->m_errorHint); + errorHint = (const char*)VARDATA(txth); + } + PG_TRY(); + { + RaiseEreport(jitContext->m_execState->m_sqlState, errorMessage, errorDetail, errorHint); + } + PG_CATCH(); + { + EmitErrorReport(); + FlushErrorState(); + } + PG_END_TRY(); + } + result = MOT::RC_ERROR; + } + return result; +} +} // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.h new file mode 100644 index 000000000..f01425b5b --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_llvm_sp.h + * JIT LLVM stored-procedure common definitions. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_llvm_sp.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef JIT_LLVM_SP_H +#define JIT_LLVM_SP_H + +/* + * ATTENTION: + * 1. Be sure to include gscodegen.h before anything else to avoid clash with PM definition in datetime.h. + * 2. Be sure to include libintl.h before gscodegen.h to avoid problem with gettext. + */ +#include "libintl.h" +#include "codegen/gscodegen.h" +#include "storage/mot/jit_exec.h" +#include "funcapi.h" + +#include "jit_common.h" +#include "jit_llvm_util.h" + +#include +#include +#include +#include + +struct PLpgSQL_function; +struct PLpgSQL_expr; + +namespace JitExec { +// forward declaration +struct JitFunctionPlan; + +using GotoLabelMap = std::map; + +struct LabelDesc { + const char* m_name; + llvm::BasicBlock* m_loopStart; + llvm::BasicBlock* m_postBlock; // either loop or block + bool m_isLoop; +}; +using LabelStack = std::list; + +/** @struct Context used for compiling llvm-jitted functions. */ +struct JitLlvmFunctionCodeGenContext : public llvm_util::LlvmCodeGenContext { + /** @var The plan used to create the jitted code. */ + JitFunctionPlan* m_plan; + + /** @var The PG compiled function. */ + PLpgSQL_function* _compiled_function; + + /** @var The function type class (for returning composite type). */ + TypeFuncClass m_typeFuncClass; + + /** @var The function result tuple descriptor (for returning composite type). */ + TupleDesc m_resultTupDesc; + + /** @var The function row tuple descriptor (for returning composite type). */ + TupleDesc m_rowTupDesc; + + // PG Types + llvm::StructType* ParamExternDataType; + llvm::StructType* ParamListInfoDataType; + llvm::StructType* TupleTableSlotType; + llvm::StructType* NumericDataType; + llvm::StructType* VarCharType; + llvm::StructType* BpCharType; + + // args + llvm::Value* m_params; + llvm::Value* m_slot; + llvm::Value* m_tuplesProcessed; + llvm::Value* m_scanEnded; + + // MOT Types + llvm::StructType* RowType; + + // function types + llvm::StructType* NullableDatumType; + + // arguments and local variables are identified by index, so we keep all of them in one bug array + std::vector m_datums; + + // arguments and local variables are identified by index, so we keep all of them in one bug array + std::vector m_datumNulls; + + /** @var The sub-transaction id with which the function started executing. */ + llvm::Value* m_initSubXid; + + // map of goto labels for each named block (label name and block name are identical, but block name might have id + // suffix) + GotoLabelMap m_gotoLabels; + LabelStack m_labelStack; + int m_exceptionBlockCount; + + bool m_usingMagicVars; + + // non-primitive default parameter constants + uint32_t m_defaultValueCount; + Const* m_defaultValues; + + // non-primitive constants in code + uint32_t m_constCount; + Const* m_constValues; + bool* m_selfManaged; + + /** @var The PG processed block (for managing SQL state and error message. */ + PLpgSQL_stmt_block* m_processedBlock; + + // helper functions +#ifdef MOT_JIT_DEBUG + llvm::FunctionCallee m_debugLogFunc; + llvm::FunctionCallee m_debugLogIntFunc; + llvm::FunctionCallee m_debugLogStringFunc; + llvm::FunctionCallee m_debugLogStringDatumFunc; + llvm::FunctionCallee m_debugLogDatumFunc; +#endif + llvm::FunctionCallee m_getCurrentSubTransactionIdFunc; + + llvm::FunctionCallee m_beginBlockWithExceptionsFunc; + llvm::FunctionCallee m_endBlockWithExceptionsFunc; + llvm::FunctionCallee m_cleanupBlockAfterExceptionFunc; + llvm::FunctionCallee m_getExceptionOriginFunc; + llvm::FunctionCallee m_setExceptionOriginFunc; + llvm::FunctionCallee m_cleanupBeforeReturnFunc; + + llvm::FunctionCallee m_convertViaStringFunc; + llvm::FunctionCallee m_castValueFunc; + llvm::FunctionCallee m_saveErrorInfoFunc; + llvm::FunctionCallee m_getErrorMessageFunc; + llvm::FunctionCallee m_getSqlStateFunc; + llvm::FunctionCallee m_getSqlStateStringFunc; + llvm::FunctionCallee m_getDatumIsNotNullFunc; + llvm::FunctionCallee m_getExprIsNullFunc; + llvm::FunctionCallee m_setExprIsNullFunc; + llvm::FunctionCallee m_getExprCollationFunc; + llvm::FunctionCallee m_setExprCollationFunc; + llvm::FunctionCallee m_execClearTupleFunc; + llvm::FunctionCallee m_execStoreVirtualTupleFunc; + llvm::FunctionCallee m_getParamAtRefFunc; + llvm::FunctionCallee m_isParamNullRefFunc; + llvm::FunctionCallee m_isCompositeResultFunc; + llvm::FunctionCallee m_createResultDatumsFunc; + llvm::FunctionCallee m_createResultNullsFunc; + llvm::FunctionCallee m_setResultValueFunc; + llvm::FunctionCallee m_createResultHeapTupleFunc; + llvm::FunctionCallee m_setSlotValueFunc; + llvm::FunctionCallee m_setSPSubQueryParamValueFunc; + llvm::FunctionCallee m_executeSubQueryFunc; + llvm::FunctionCallee m_releaseNonJitSubQueryResourcesFunc; + llvm::FunctionCallee m_getTuplesProcessedFunc; + llvm::FunctionCallee m_getSubQuerySlotValueFunc; + llvm::FunctionCallee m_getSubQuerySlotIsNullFunc; + llvm::FunctionCallee m_getSubQueryResultHeapTupleFunc; + llvm::FunctionCallee m_getHeapTupleValueFunc; + llvm::FunctionCallee m_getSpiResultFunc; + llvm::FunctionCallee m_setTpProcessedFunc; + llvm::FunctionCallee m_setScanEndedFunc; + llvm::FunctionCallee m_getConstAtFunc; + llvm::FunctionCallee m_abortFunctionFunc; + llvm::FunctionCallee m_llvmClearExceptionStackFunc; + llvm::FunctionCallee m_emitProfileDataFunc; +}; +} // namespace JitExec + +#endif /* JIT_LLVM_SP_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp_codegen.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp_codegen.h new file mode 100644 index 000000000..4accd7221 --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_sp_codegen.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_llvm_sp_codegen.h + * LLVM JIT-compiled code generation for stored procedures. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_llvm_sp_codegen.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef JIT_LLVM_SP_CODEGEN_H +#define JIT_LLVM_SP_CODEGEN_H + +#include "postgres.h" +#include "nodes/parsenodes.h" + +#include "storage/mot/jit_def.h" +#include "mot_engine.h" + +namespace JitExec { +// forward declarations +struct MotJitContext; +struct JitPlan; + +/** + * @brief Generates a native LLVM JIT-compiled code for a stored procedure. + * @param function The parsed function. + * @param procTuple The stored procedure entry in the system catalog. + * @param functionOid The function identifier. + * @param returnSetInfo Return set information for the function (required during parsing). + * @param plan The plan resulted from the analysis phase. + * @param[out] codegenStats Code generation statistics. + * @return The resulting JIT context, or NULL if failed. + */ +extern MotJitContext* JitCodegenLlvmFunction(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, + ReturnSetInfo* returnSetInfo, JitPlan* plan, JitCodegenStats& codegenStats); +} // namespace JitExec + +#endif /* JIT_LLVM_SP_CODEGEN_H */ \ No newline at end of file diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.cpp b/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.cpp index b5723ef3e..7c5489a21 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.cpp @@ -29,15 +29,571 @@ #include "utils/elog.h" #include "securec.h" -using namespace dorado; - -namespace llvm { -DECLARE_LOGGER(JitUtil, JitExec); - -// Globals +// Global definitions #define IF_STACK u_sess->mot_cxt.jit_llvm_if_stack +#define LOOP_STACK u_sess->mot_cxt.jit_llvm_loop_stack #define WHILE_STACK u_sess->mot_cxt.jit_llvm_while_stack #define DO_WHILE_STACK u_sess->mot_cxt.jit_llvm_do_while_stack +#define FOR_STACK u_sess->mot_cxt.jit_llvm_for_stack +#define SWITCH_CASE_STACK u_sess->mot_cxt.jit_llvm_switch_case_stack +#define TRY_CATCH_STACK u_sess->mot_cxt.jit_llvm_try_catch_stack + +namespace llvm_util { +DECLARE_LOGGER(JitUtil, JitExec); + +extern const char* LlvmRuntimeFaultToString(int faultCode) +{ + if (JitExec::IsJitRuntimeFaultCode(faultCode)) { + return JitExec::JitRuntimeFaultToString(faultCode); + } + + switch (faultCode) { + case LLVM_FAULT_ACCESS_VIOLATION: + return "Access violation"; + + case LLVM_FAULT_UNHANDLED_EXCEPTION: + return "Unhandled exception"; + + case LLVM_FAULT_RESOURCE_LIMIT: + return "Resource limit"; + + default: + return "Applicative fault code"; + } +} + +extern void OpenCompileFrame(LlvmCodeGenContext* ctx) +{ + ctx->m_currentCompileFrame = new (std::nothrow) CompileFrame(ctx->m_currentCompileFrame); + if (ctx->m_currentCompileFrame == nullptr) { + MOT_LOG_TRACE("Failed to allocate compile frame"); + } +} + +extern void CloseCompileFrame(LlvmCodeGenContext* ctx) +{ + // cleanup current frame + if (ctx->m_currentCompileFrame != nullptr) { + CompileFrame* compileFrame = ctx->m_currentCompileFrame->GetNext(); + delete ctx->m_currentCompileFrame; + ctx->m_currentCompileFrame = compileFrame; + } +} + +extern void CleanupCompileFrames(LlvmCodeGenContext* ctx) +{ + while (ctx->m_currentCompileFrame != nullptr) { + CloseCompileFrame(ctx); + } +} + +extern llvm::FunctionCallee DefineFunction(llvm::Module* module, llvm::Type* returnType, const char* name, ...) +{ + va_list vargs; + va_start(vargs, name); + std::vector args; + llvm::Type* argType = va_arg(vargs, llvm::Type*); + while (argType != nullptr) { + args.push_back(argType); + argType = va_arg(vargs, llvm::Type*); + } + va_end(vargs); + llvm::ArrayRef argsRef(args); + llvm::FunctionType* funcType = llvm::FunctionType::get(returnType, argsRef, false); + return module->getOrInsertFunction(name, funcType); +} + +extern llvm::Value* AddFunctionCall(LlvmCodeGenContext* ctx, llvm::FunctionCallee func, ...) +{ + // prepare arguments + va_list vargs; + va_start(vargs, func); + std::vector args; + llvm::Value* argValue = va_arg(vargs, llvm::Value*); + while (argValue != nullptr) { + args.push_back(argValue); + argValue = va_arg(vargs, llvm::Value*); + } + va_end(vargs); + llvm::ArrayRef argsRef(args); + + // make the call + return ctx->m_builder->CreateCall(func, argsRef); +} + +extern llvm::Value* InsertFunctionCall( + llvm::Instruction* before, LlvmCodeGenContext* ctx, llvm::FunctionCallee func, ...) +{ + // find insert position + llvm::BasicBlock* block = ctx->m_builder->GetInsertBlock(); + llvm::BasicBlock::InstListType& instList = block->getInstList(); + llvm::BasicBlock::iterator itr = block->begin(); + while (itr != block->end()) { + if (itr == before->getIterator()) { + break; + } + ++itr; + } + if (itr == block->end()) { + MOT_LOG_ERROR("Failed to insert function call before instruction: instruction not found in current block"); + return nullptr; + } + + // prepare arguments + va_list vargs; + va_start(vargs, func); + std::vector args; + llvm::Value* argValue = va_arg(vargs, llvm::Value*); + while (argValue != nullptr) { + args.push_back(argValue); + argValue = va_arg(vargs, llvm::Value*); + } + va_end(vargs); + llvm::ArrayRef argsRef(args); + + // prepare the call + llvm::CallInst* callInst = llvm::CallInst::Create(func, args); + + // an insert the call before the required instruction + instList.insert(itr, callInst); + return callInst; +} + +extern llvm::Value* AddInvokePGFunction0(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId) +{ + llvm::ConstantInt* funcValue = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)funcPtr, true); + llvm::Value* funcPtrValue = llvm::ConstantExpr::getIntToPtr(funcValue, ctx->STR_T); + return AddFunctionCall(ctx, ctx->m_invokePGFunction0Func, funcPtrValue, JIT_CONST_INT32(collationId), nullptr); +} + +extern llvm::Value* AddInvokePGFunction1(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg, llvm::Value* isNull, Oid argType) +{ + llvm::ConstantInt* funcValue = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)funcPtr, true); + llvm::Value* funcPtrValue = llvm::ConstantExpr::getIntToPtr(funcValue, ctx->STR_T); + return AddFunctionCall(ctx, + ctx->m_invokePGFunction1Func, + funcPtrValue, + JIT_CONST_INT32(collationId), + JIT_CONST_INT32((isStrict ? 1 : 0)), + arg, + isNull, + JIT_CONST_INT32(argType), + nullptr); +} + +extern llvm::Value* AddInvokePGFunction2(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg1, llvm::Value* isNull1, Oid argType1, llvm::Value* arg2, llvm::Value* isNull2, Oid argType2) +{ + llvm::ConstantInt* funcValue = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)funcPtr, true); + llvm::Value* funcPtrValue = llvm::ConstantExpr::getIntToPtr(funcValue, ctx->STR_T); + return AddFunctionCall(ctx, + ctx->m_invokePGFunction2Func, + funcPtrValue, + JIT_CONST_INT32(collationId), + JIT_CONST_INT32((isStrict ? 1 : 0)), + arg1, + isNull1, + JIT_CONST_INT32(argType1), + arg2, + isNull2, + JIT_CONST_INT32(argType2), + nullptr); +} + +extern llvm::Value* AddInvokePGFunction3(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg1, llvm::Value* isNull1, Oid argType1, llvm::Value* arg2, llvm::Value* isNull2, Oid argType2, + llvm::Value* arg3, llvm::Value* isNull3, Oid argType3) +{ + llvm::ConstantInt* funcValue = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)funcPtr, true); + llvm::Value* funcPtrValue = llvm::ConstantExpr::getIntToPtr(funcValue, ctx->STR_T); + return AddFunctionCall(ctx, + ctx->m_invokePGFunction3Func, + funcPtrValue, + JIT_CONST_INT32(collationId), + JIT_CONST_INT32((isStrict ? 1 : 0)), + arg1, + isNull1, + JIT_CONST_INT32(argType1), + arg2, + isNull2, + JIT_CONST_INT32(argType2), + arg3, + isNull3, + JIT_CONST_INT32(argType3), + nullptr); +} + +extern llvm::Value* AddInvokePGFunctionN(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value** args, llvm::Value** isNull, Oid* argTypes, int argCount) +{ + llvm::ConstantInt* funcValue = llvm::ConstantInt::get(ctx->INT64_T, (int64_t)funcPtr, true); + llvm::Value* funcPtrValue = llvm::ConstantExpr::getIntToPtr(funcValue, ctx->STR_T); + + // allocate arrays + llvm::Value* argArray = AddMemSessionAlloc(ctx, JIT_CONST_INT32(sizeof(Datum) * argCount)); + llvm::Value* nullArray = AddMemSessionAlloc(ctx, JIT_CONST_INT32(sizeof(uint32_t) * argCount)); + llvm::Value* typeArray = AddMemSessionAlloc(ctx, JIT_CONST_INT32(sizeof(uint32_t) * argCount)); + + // assign arrays + for (int i = 0; i < argCount; ++i) { + llvm::Value* elmIndex = JIT_CONST_INT32(i); + llvm::Value* elm = ctx->m_builder->CreateGEP(argArray, elmIndex); + ctx->m_builder->CreateStore(args[i], elm, true); + elm = ctx->m_builder->CreateGEP(nullArray, elmIndex); + ctx->m_builder->CreateStore(isNull[i], elm, true); + elm = ctx->m_builder->CreateGEP(typeArray, elmIndex); + ctx->m_builder->CreateStore(JIT_CONST_INT32(argTypes[i]), elm, true); + } + + // make the call + llvm::Value* result = AddFunctionCall(ctx, + ctx->m_invokePGFunctionNFunc, + funcPtrValue, + JIT_CONST_INT32(collationId), + JIT_CONST_INT32((isStrict ? 1 : 0)), + argArray, + nullArray, + typeArray, + JIT_CONST_INT32(argCount), + nullptr); + + // clean up + AddMemSessionFree(ctx, argArray); + AddMemSessionFree(ctx, nullArray); + AddMemSessionFree(ctx, typeArray); + + return result; +} + +extern llvm::Value* AddMemSessionAlloc(LlvmCodeGenContext* ctx, llvm::Value* allocSize) +{ + return AddFunctionCall(ctx, ctx->m_memSessionAllocFunc, allocSize); +} + +extern void AddMemSessionFree(LlvmCodeGenContext* ctx, llvm::Value* ptr) +{ + AddFunctionCall(ctx, ctx->m_memSessionFreeFunc, ptr); +} + +inline void DefineLlvmPushExceptionFrame(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmPushExceptionFrameFunc = + DefineFunction(module, ctx->INT8_T->getPointerTo(), "LlvmPushExceptionFrame", nullptr); +} + +inline void DefineLlvmGetCurrentExceptionFrame(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmGetCurrentExceptionFrameFunc = + DefineFunction(module, ctx->INT8_T->getPointerTo(), "LlvmGetCurrentExceptionFrame", nullptr); +} + +inline void DefineLlvmPopExceptionFrame(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmPopExceptionFrameFunc = DefineFunction(module, ctx->INT32_T, "LlvmPopExceptionFrame", nullptr); +} + +inline void DefineLlvmThrowException(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmThrowExceptionFunc = DefineFunction(module, ctx->VOID_T, "LlvmThrowException", ctx->INT32_T, nullptr); +} + +inline void DefineLlvmRethrowException(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmRethrowExceptionFunc = DefineFunction(module, ctx->VOID_T, "LlvmRethrowException", nullptr); +} + +inline void DefineLlvmGetExceptionValue(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmGetExceptionValueFunc = DefineFunction(module, ctx->INT32_T, "LlvmGetExceptionValue", nullptr); +} + +inline void DefineLlvmGetExceptionStatus(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmGetExceptionStatusFunc = DefineFunction(module, ctx->INT32_T, "LlvmGetExceptionStatus", nullptr); +} + +inline void DefineLlvmResetExceptionStatus(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmResetExceptionStatusFunc = DefineFunction(module, ctx->VOID_T, "LlvmResetExceptionStatus", nullptr); +} + +inline void DefineLlvmResetExceptionValue(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmResetExceptionValueFunc = DefineFunction(module, ctx->VOID_T, "LlvmResetExceptionValue", nullptr); +} + +inline void DefineLlvmUnwindExceptionFrame(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmUnwindExceptionFrameFunc = DefineFunction(module, ctx->VOID_T, "LlvmUnwindExceptionFrame", nullptr); +} + +inline void DefineLlvmSetJmp(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmSetJmpFunc = llvm_util::DefineFunction( + module, ctx->INT32_T, "__sigsetjmp", ctx->INT8_T->getPointerTo(), ctx->INT32_T, nullptr); +} + +inline void DefineLlvmLongJmp(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmLongJmpFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "siglongjmp", ctx->INT8_T->getPointerTo(), ctx->INT32_T, nullptr); +} + +#ifdef MOT_JIT_DEBUG +inline void DefineLlvmDebugPrint(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmDebugPrintFunc = + llvm_util::DefineFunction(module, ctx->VOID_T, "debugLog", ctx->STR_T, ctx->STR_T, nullptr); +} + +inline void DefineLlvmDebugPrintFrame(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_llvmDebugPrintFrameFunc = llvm_util::DefineFunction( + module, ctx->VOID_T, "LLvmPrintFrame", ctx->STR_T, ctx->INT8_T->getPointerTo(), nullptr); +} +#endif + +inline void DefineInvokePGFunction0(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_invokePGFunction0Func = DefineFunction( + module, ctx->DATUM_T, "JitInvokePGFunction0", ctx->INT8_T->getPointerTo(), ctx->INT32_T, nullptr); +} + +inline void DefineInvokePGFunction1(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_invokePGFunction1Func = DefineFunction(module, + ctx->DATUM_T, + "JitInvokePGFunction1", + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineInvokePGFunction2(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_invokePGFunction2Func = DefineFunction(module, + ctx->DATUM_T, + "JitInvokePGFunction2", + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineInvokePGFunction3(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_invokePGFunction3Func = DefineFunction(module, + ctx->DATUM_T, + "JitInvokePGFunction3", + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T, + ctx->INT32_T, + ctx->INT32_T, + nullptr); +} + +inline void DefineInvokePGFunctionN(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_invokePGFunctionNFunc = DefineFunction(module, + ctx->DATUM_T, + "JitInvokePGFunctionN", + ctx->INT8_T->getPointerTo(), + ctx->INT32_T, + ctx->INT32_T, + ctx->DATUM_T->getPointerTo(), + ctx->INT32_T->getPointerTo(), + ctx->INT32_T->getPointerTo(), + ctx->INT32_T, + nullptr); +} + +inline void DefineMemSessionAlloc(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_memSessionAllocFunc = + DefineFunction(module, ctx->INT8_T->getPointerTo(), "JitMemSessionAlloc", ctx->INT32_T, nullptr); +} + +inline void DefineMemSessionFree(LlvmCodeGenContext* ctx, llvm::Module* module) +{ + ctx->m_memSessionFreeFunc = + DefineFunction(module, ctx->VOID_T, "JitMemSessionFree", ctx->INT8_T->getPointerTo(), nullptr); +} + +inline llvm::Value* AddLlvmPushExceptionFrame(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmPushExceptionFrameFunc, nullptr); +} + +inline llvm::Value* AddLlvmGetCurrentExceptionFrame(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmGetCurrentExceptionFrameFunc, nullptr); +} + +inline llvm::Value* AddLlvmPopExceptionFrame(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmPopExceptionFrameFunc, nullptr); +} + +inline llvm::Value* AddLlvmThrowException(LlvmCodeGenContext* ctx, llvm::Value* exceptionValue) +{ + return AddFunctionCall(ctx, ctx->m_llvmThrowExceptionFunc, exceptionValue, nullptr); +} + +inline llvm::Value* AddLlvmRethrowException(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmRethrowExceptionFunc, nullptr); +} + +inline llvm::Value* AddLlvmGetExceptionValue(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmGetExceptionValueFunc, nullptr); +} + +inline llvm::Value* AddLlvmGetExceptionStatus(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmGetExceptionStatusFunc, nullptr); +} + +inline void AddLlvmResetExceptionStatus(LlvmCodeGenContext* ctx) +{ + AddFunctionCall(ctx, ctx->m_llvmResetExceptionStatusFunc, nullptr); +} + +inline void AddLlvmResetExceptionValue(LlvmCodeGenContext* ctx) +{ + AddFunctionCall(ctx, ctx->m_llvmResetExceptionValueFunc, nullptr); +} + +inline llvm::Value* AddLlvmUnwindExceptionFrame(LlvmCodeGenContext* ctx) +{ + return AddFunctionCall(ctx, ctx->m_llvmUnwindExceptionFrameFunc, nullptr); +} + +#ifdef MOT_JIT_DEBUG +inline void AddLlvmDebugPrintImpl(LlvmCodeGenContext* ctx, const char* function, const char* msg) +{ + llvm::Value* functionValue = ctx->m_builder->CreateGlobalStringPtr(function); + llvm::Value* msgValue = ctx->m_builder->CreateGlobalStringPtr(msg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_llvmDebugPrintFunc, functionValue, msgValue, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_llvmDebugPrintFunc, functionValue, msgValue, nullptr); + } +} + +inline void AddLlvmDebugPrintFrameImpl(LlvmCodeGenContext* ctx, const char* msg, llvm::Value* frame) +{ + llvm::Value* msgValue = ctx->m_builder->CreateGlobalStringPtr(msg); + + // a debug call may sometimes be emitted after terminator (return, branch, etc.), so we need to inject it before + // the terminator + llvm::Instruction* terminator = ctx->m_builder->GetInsertBlock()->getTerminator(); + if (terminator == nullptr) { + llvm_util::AddFunctionCall(ctx, ctx->m_llvmDebugPrintFrameFunc, msgValue, frame, nullptr); + } else { + llvm_util::InsertFunctionCall(terminator, ctx, ctx->m_llvmDebugPrintFrameFunc, msgValue, frame, nullptr); + } +} +#define LLVM_DEBUG_PRINT(ctx, function, msg) AddLlvmDebugPrintImpl(ctx, function, msg) +#define LLVM_DEBUG_PRINT_FRAME(ctx, msg, frame) AddLlvmDebugPrintFrameImpl(ctx, msg, frame) +#else +#define LLVM_DEBUG_PRINT(ctx, function, msg) +#define LLVM_DEBUG_PRINT_FRAME(ctx, msg, frame) +#endif + +inline llvm::Value* AddLlvmSetJmp(LlvmCodeGenContext* ctx, llvm::Value* setJumpBuf) +{ + return llvm_util::AddFunctionCall(ctx, ctx->m_llvmSetJmpFunc, setJumpBuf, JIT_CONST_INT32(1), nullptr); +} + +inline void AddLlvmLongJmp(LlvmCodeGenContext* ctx, llvm::Value* setJumpBuf, llvm::Value* exceptionValue) +{ + llvm_util::AddFunctionCall(ctx, ctx->m_llvmLongJmpFunc, setJumpBuf, exceptionValue, nullptr); +} + +extern void InitLlvmCodeGenContext(LlvmCodeGenContext* ctx, GsCodeGen* codeGen, GsCodeGen::LlvmBuilder* builder) +{ + ctx->m_codeGen = codeGen; + ctx->m_builder = builder; + + llvm::LLVMContext& context = ctx->m_codeGen->context(); + + // primitive types + ctx->INT1_T = llvm::Type::getInt1Ty(context); + ctx->INT8_T = llvm::Type::getInt8Ty(context); + ctx->INT16_T = llvm::Type::getInt16Ty(context); + ctx->INT32_T = llvm::Type::getInt32Ty(context); + ctx->INT64_T = llvm::Type::getInt64Ty(context); + ctx->VOID_T = llvm::Type::getVoidTy(context); + ctx->STR_T = llvm::Type::getInt8Ty(context)->getPointerTo(); + ctx->FLOAT_T = llvm::Type::getFloatTy(context); + ctx->DOUBLE_T = llvm::Type::getDoubleTy(context); + ctx->BOOL_T = ctx->INT8_T; + ctx->DATUM_T = ctx->INT64_T; + + llvm::Module* module = ctx->m_codeGen->module(); + + // exception helper functions + DefineLlvmPushExceptionFrame(ctx, module); + DefineLlvmGetCurrentExceptionFrame(ctx, module); + DefineLlvmPopExceptionFrame(ctx, module); + DefineLlvmThrowException(ctx, module); + DefineLlvmRethrowException(ctx, module); + DefineLlvmGetExceptionValue(ctx, module); + DefineLlvmGetExceptionStatus(ctx, module); + DefineLlvmResetExceptionStatus(ctx, module); + DefineLlvmResetExceptionValue(ctx, module); + DefineLlvmUnwindExceptionFrame(ctx, module); + DefineLlvmSetJmp(ctx, module); + DefineLlvmLongJmp(ctx, module); +#ifdef MOT_JIT_DEBUG + DefineLlvmDebugPrint(ctx, module); + DefineLlvmDebugPrintFrame(ctx, module); +#endif + DefineInvokePGFunction0(ctx, module); + DefineInvokePGFunction1(ctx, module); + DefineInvokePGFunction2(ctx, module); + DefineInvokePGFunction3(ctx, module); + DefineInvokePGFunctionN(ctx, module); + + DefineMemSessionAlloc(ctx, module); + DefineMemSessionFree(ctx, module); + + ctx->m_currentCompileFrame = nullptr; +} + +extern void DestroyLlvmCodeGenContext(LlvmCodeGenContext* ctx) +{ + CleanupCompileFrames(ctx); + if (ctx->m_codeGen != nullptr) { + ctx->m_codeGen->releaseResource(); + delete ctx->m_codeGen; + ctx->m_codeGen = nullptr; + } +} char* JitUtils::FormatBlockName(char* buf, size_t size, const char* baseName, const char* suffix) { @@ -49,7 +605,7 @@ char* JitUtils::FormatBlockName(char* buf, size_t size, const char* baseName, co llvm::Value* JitUtils::ExecCompare( GsCodeGen::LlvmBuilder* builder, llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpOp cmpOp) { - llvm::Value* res = NULL; + llvm::Value* res = nullptr; switch (cmpOp) { case JitExec::JIT_ICMP_EQ: res = builder->CreateICmpEQ(lhs, rhs); @@ -60,33 +616,33 @@ llvm::Value* JitUtils::ExecCompare( break; case JitExec::JIT_ICMP_GT: - res = builder->CreateICmpUGT(lhs, rhs); + res = builder->CreateICmpSGT(lhs, rhs); break; case JitExec::JIT_ICMP_GE: - res = builder->CreateICmpUGE(lhs, rhs); + res = builder->CreateICmpSGE(lhs, rhs); break; case JitExec::JIT_ICMP_LT: - res = builder->CreateICmpULT(lhs, rhs); + res = builder->CreateICmpSLT(lhs, rhs); break; case JitExec::JIT_ICMP_LE: - res = builder->CreateICmpULE(lhs, rhs); + res = builder->CreateICmpSLE(lhs, rhs); break; default: MOT_REPORT_ERROR( MOT_ERROR_INTERNAL, "Execute Pseudo-JIT Function", "Invalid compare operator %d", (int)cmpOp); MOT_ASSERT(false); - return NULL; + return nullptr; } return res; } llvm::Value* JitUtils::GetTypedZero(const llvm::Value* value) { - llvm::Value* typedZero = NULL; + llvm::Value* typedZero = nullptr; if (value->getType()->isPointerTy()) { typedZero = llvm::ConstantPointerNull::get((llvm::PointerType*)value->getType()); } else { @@ -95,12 +651,25 @@ llvm::Value* JitUtils::GetTypedZero(const llvm::Value* value) return typedZero; } -JitStatement::JitStatement(llvm::LLVMContext& context, GsCodeGen::LlvmBuilder* builder) - : m_context(context), m_builder(builder) +JitStatement::JitStatement(LlvmCodeGenContext* codegenContext) + : m_codegenContext(codegenContext), + m_context(m_codegenContext->m_codeGen->context()), + m_builder(m_codegenContext->m_builder) {} JitStatement::~JitStatement() -{} +{ + m_codegenContext = nullptr; + m_builder = nullptr; +} + +llvm::BasicBlock* JitStatement::MakeBlock(const char* blockBaseName, const char* suffix) +{ + constexpr size_t bufSize = 64; + char buf[bufSize]; + return llvm::BasicBlock::Create( + m_context, JitUtils::FormatBlockName(buf, bufSize, blockBaseName, suffix), m_codegenContext->m_jittedFunction); +} void JitStatement::EvalCmp(llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpOp cmpOp, llvm::BasicBlock* successBlock, llvm::BasicBlock* failBlock) @@ -133,39 +702,25 @@ bool JitStatement::CurrentBlockEndsInBranch() return result; } -JitIf::JitIf( - llvm::LLVMContext& context, GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, const char* blockName) - : JitStatement(context, builder), m_elseBlockUsed(false) +JitIf::JitIf(LlvmCodeGenContext* codegenContext, const char* blockName) + : JitStatement(codegenContext), m_elseBlockUsed(false) { - const unsigned int bufSize = 64; - char buf[bufSize]; - m_condBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_if_cond_bb"), jittedFunction); - m_ifBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_if_exec_bb"), jittedFunction); - m_elseBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_else_exec_bb"), jittedFunction); - m_postIfBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_if_bb"), jittedFunction); + m_ifBlock = MakeBlock(blockName, "_if_exec_bb"); + m_elseBlock = MakeBlock(blockName, "_else_exec_bb"); + m_postIfBlock = MakeBlock(blockName, "_post_if_bb"); - // we end current block and start the condition evaluation block - m_builder->CreateBr(m_condBlock); - m_builder->SetInsertPoint(m_condBlock); - - // push this object on top of the if-stack + // push this object on top of the compile-time if-stack m_next = IF_STACK; IF_STACK = this; } JitIf::~JitIf() { - // pop this object from the if-stack + // pop this object from the compile-time if-stack IF_STACK = IF_STACK->m_next; - m_condBlock = nullptr; m_ifBlock = nullptr; m_elseBlock = nullptr; m_postIfBlock = nullptr; - m_builder = nullptr; m_next = nullptr; } @@ -177,16 +732,19 @@ JitIf* JitIf::GetCurrentStatement() void JitIf::JitIfCmp(llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpOp cmpOp) { EvalCmp(lhs, rhs, cmpOp, m_ifBlock, m_elseBlock); + OpenCompileFrame(m_codegenContext); // open if-block compile frame } void JitIf::JitIfEval(llvm::Value* value) { Eval(value, m_ifBlock, m_elseBlock); + OpenCompileFrame(m_codegenContext); // open if-block compile frame } void JitIf::JitIfNot(llvm::Value* value) { EvalNot(value, m_ifBlock, m_elseBlock); + OpenCompileFrame(m_codegenContext); // open if-block compile frame } void JitIf::JitElse() @@ -196,7 +754,9 @@ void JitIf::JitElse() if (!CurrentBlockEndsInBranch()) { m_builder->CreateBr(m_postIfBlock); } + CloseCompileFrame(m_codegenContext); // close if-block compile frame m_builder->SetInsertPoint(m_elseBlock); + OpenCompileFrame(m_codegenContext); // open else-block compile frame m_elseBlockUsed = true; } @@ -207,11 +767,12 @@ void JitIf::JitEnd() if (!CurrentBlockEndsInBranch()) { m_builder->CreateBr(m_postIfBlock); } + CloseCompileFrame(m_codegenContext); // close recent compile frame if (!m_elseBlockUsed) { // user did not use else block, so we must generate an empty one, otherwise it will be removed // during function finalization (since it is empty), and if the condition evaluation fails, then - // during runtime we will jump to a block that was deleted (core dump) + // during run-time we will jump to a block that was deleted (core dump) m_builder->SetInsertPoint(m_elseBlock); m_builder->CreateBr(m_postIfBlock); } @@ -220,31 +781,42 @@ void JitIf::JitEnd() m_builder->SetInsertPoint(m_postIfBlock); } -JitWhile::JitWhile( - llvm::LLVMContext& context, GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, const char* blockName) - : JitStatement(context, builder) +JitLoop::JitLoop(LlvmCodeGenContext* codegenContext, const char* name) : JitStatement(codegenContext), m_name(name) { - const unsigned int bufSize = 64; - char buf[bufSize]; - m_condWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_cond_while_bb"), jittedFunction); - m_execWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_exec_while_bb"), jittedFunction); - m_postWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_while_bb"), jittedFunction); + // push this object on top of the compile-time loop-stack + m_next = LOOP_STACK; + LOOP_STACK = this; +} + +JitLoop::~JitLoop() +{ + // pop this object from the compile-time loop-stack + LOOP_STACK = LOOP_STACK->m_next; +} + +JitLoop* JitLoop::GetCurrentStatement() +{ + return LOOP_STACK; +} + +JitWhile::JitWhile(LlvmCodeGenContext* codegenContext, const char* blockName) : JitLoop(codegenContext, blockName) +{ + m_condWhileBlock = MakeBlock(blockName, "_cond_while_bb"); + m_execWhileBlock = MakeBlock(blockName, "_exec_while_bb"); + m_postWhileBlock = MakeBlock(blockName, "_post_while_bb"); // we end current block and start the condition evaluation block m_builder->CreateBr(m_condWhileBlock); m_builder->SetInsertPoint(m_condWhileBlock); - // push this object on top of the while-stack + // push this object on top of the compile-time while-stack m_next = WHILE_STACK; WHILE_STACK = this; } JitWhile::~JitWhile() { - // pop this object from the while-stack + // pop this object from the compile-time while-stack WHILE_STACK = WHILE_STACK->m_next; m_condWhileBlock = nullptr; m_execWhileBlock = nullptr; @@ -261,16 +833,19 @@ void JitWhile::JitWhileCmp(llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpO { // evaluate condition and start while exec block EvalCmp(lhs, rhs, cmpOp, m_execWhileBlock, m_postWhileBlock); + OpenCompileFrame(m_codegenContext); // open while-block compile frame } void JitWhile::JitWhileEval(llvm::Value* value) { Eval(value, m_execWhileBlock, m_postWhileBlock); + OpenCompileFrame(m_codegenContext); // open while-block compile frame } void JitWhile::JitWhileNot(llvm::Value* value) { EvalNot(value, m_execWhileBlock, m_postWhileBlock); + OpenCompileFrame(m_codegenContext); // open while-block compile frame } void JitWhile::JitContinue() @@ -289,34 +864,29 @@ void JitWhile::JitEnd() { // insert instruction to jump back to test loop and begin code after loop m_builder->CreateBr(m_condWhileBlock); + CloseCompileFrame(m_codegenContext); // close while-block compile frame m_builder->SetInsertPoint(m_postWhileBlock); } -JitDoWhile::JitDoWhile( - llvm::LLVMContext& context, GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, const char* blockName) - : JitStatement(context, builder) +JitDoWhile::JitDoWhile(LlvmCodeGenContext* codegenContext, const char* blockName) : JitLoop(codegenContext, blockName) { - const unsigned int bufSize = 64; - char buf[bufSize]; - m_execWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_exec_while_bb"), jittedFunction); - m_condWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_cond_while_bb"), jittedFunction); - m_postWhileBlock = llvm::BasicBlock::Create( - m_context, JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_while_bb"), jittedFunction); + m_execWhileBlock = MakeBlock(blockName, "_exec_while_bb"); + m_condWhileBlock = MakeBlock(blockName, "_cond_while_bb"); + m_postWhileBlock = MakeBlock(blockName, "_post_while_bb"); // we end current block and start the do-while execution block m_builder->CreateBr(m_execWhileBlock); m_builder->SetInsertPoint(m_execWhileBlock); + OpenCompileFrame(m_codegenContext); // open do-while-block compile frame - // push this object on top of the do-while-stack + // push this object on top of the compile-time do-while-stack m_next = DO_WHILE_STACK; DO_WHILE_STACK = this; } JitDoWhile::~JitDoWhile() { - // pop this object from the while-stack + // pop this object from the compile-time do-while-stack DO_WHILE_STACK = DO_WHILE_STACK->m_next; m_execWhileBlock = nullptr; m_condWhileBlock = nullptr; @@ -366,6 +936,409 @@ void JitDoWhile::JitWhileNot(llvm::Value* value) void JitDoWhile::JitEnd() { // no need to jump to cond block + CloseCompileFrame(m_codegenContext); // close do-while-block compile frame m_builder->SetInsertPoint(m_postWhileBlock); } -} // namespace llvm + +JitFor::JitFor(LlvmCodeGenContext* codegenContext, const char* blockName, const char* counterName, + llvm::Value* initValue, llvm::Value* step, llvm::Value* bound, JitExec::JitICmpOp cmpOp) + : JitLoop(codegenContext, blockName), m_step(step), m_bound(bound), m_cmpOp(cmpOp) +{ + Init(blockName, counterName, nullptr, initValue); +} + +JitFor::JitFor(LlvmCodeGenContext* codegenContext, const char* blockName, llvm::Value* counterVar, + llvm::Value* initValue, llvm::Value* step, llvm::Value* bound, JitExec::JitICmpOp cmpOp) + : JitLoop(codegenContext, blockName), m_step(step), m_bound(bound), m_cmpOp(cmpOp) +{ + Init(blockName, nullptr, counterVar, initValue); +} + +JitFor::~JitFor() +{ + // pop this object from the compile-time for-stack + FOR_STACK = FOR_STACK->m_next; +} + +JitFor* JitFor::GetCurrentStatement() +{ + return FOR_STACK; +} + +llvm::Value* JitFor::GetCounter() +{ + return m_builder->CreateLoad(m_counterValue, true); +} + +void JitFor::JitContinue() +{ + // jump to test condition block + m_builder->CreateBr(m_incrementForCounterBlock); +} + +void JitFor::JitBreak() +{ + m_builder->CreateBr(m_postForBlock); +} + +void JitFor::JitEnd() +{ + // jump to test condition block + m_builder->CreateBr(m_incrementForCounterBlock); + CloseCompileFrame(m_codegenContext); // close for-block compile frame + m_builder->SetInsertPoint(m_postForBlock); +} + +void JitFor::Init(const char* blockName, const char* counterName, llvm::Value* counterVar, llvm::Value* initValue) +{ + m_initForCounterBlock = MakeBlock(blockName, "_init_for_counter_bb"); + m_condForBlock = MakeBlock(blockName, "_cond_for_bb"); + m_execForBodyBlock = MakeBlock(blockName, "_exec_for_body_bb"); + m_incrementForCounterBlock = MakeBlock(blockName, "_increment_for_counter_bb"); + m_postForBlock = MakeBlock(blockName, "_post_while_bb"); + + // we end current block and start the do-while execution block + m_builder->CreateBr(m_initForCounterBlock); + m_builder->SetInsertPoint(m_initForCounterBlock); + OpenCompileFrame(m_codegenContext); // open for-block compile frame + + // generate create local variable and assign instructions + if (counterName) { + m_counterValue = m_builder->CreateAlloca(initValue->getType(), 0, nullptr, counterName); + } else { + MOT_ASSERT(counterVar); + m_counterValue = counterVar; + } + m_builder->CreateStore(initValue, m_counterValue); + m_builder->CreateBr(m_condForBlock); + + // generate check counter block + m_builder->SetInsertPoint(m_condForBlock); + { + JitIf jitIf(m_codegenContext, "for_cond"); + llvm::LoadInst* counterValue = m_builder->CreateLoad(m_counterValue); + jitIf.JitIfCmp(counterValue, m_bound, m_cmpOp); + m_builder->CreateBr(m_execForBodyBlock); + jitIf.JitElse(); + m_builder->CreateBr(m_postForBlock); + jitIf.JitEnd(); + } + + // generate increment counter block + m_builder->CreateBr(m_incrementForCounterBlock); + m_builder->SetInsertPoint(m_incrementForCounterBlock); + llvm::LoadInst* counterValue = m_builder->CreateLoad(m_counterValue); + llvm::Value* newCounterValue = m_builder->CreateAdd(counterValue, m_step); + m_builder->CreateStore(newCounterValue, m_counterValue); + m_builder->CreateBr(m_condForBlock); + + // allow user now to define for body + m_builder->SetInsertPoint(m_execForBodyBlock); + + // push this object on top of the compile-time for-stack + m_next = FOR_STACK; + FOR_STACK = this; +} + +JitSwitchCase::JitSwitchCase(LlvmCodeGenContext* codegenContext, const char* blockName, llvm::Value* switchValue) + : JitStatement(codegenContext), + m_blockName(blockName), + m_switchValue(switchValue), + m_caseBlock(nullptr), + m_postCaseBlock(nullptr) +{ + m_defaultCaseBlock = MakeBlock(blockName, "_default_case_bb"); + m_postSwitchBlock = MakeBlock(blockName, "_post_switch_bb"); + + // push this object on top of the compile-time switch-stack + m_next = SWITCH_CASE_STACK; + SWITCH_CASE_STACK = this; +} + +JitSwitchCase::~JitSwitchCase() +{ + // pop this object from the compile-time switch-stack + SWITCH_CASE_STACK = SWITCH_CASE_STACK->m_next; +} + +JitSwitchCase* JitSwitchCase::GetCurrentStatement() +{ + return SWITCH_CASE_STACK; +} + +void JitSwitchCase::JitCase(llvm::Value* caseValue) +{ + JitCaseMany(caseValue); + JitCaseManyTerm(); +} + +void JitSwitchCase::JitCaseMany(llvm::Value* caseValue) +{ + // generate block for case body + if (m_caseBlock == nullptr) { + MOT_ASSERT(m_postCaseBlock == nullptr); + m_caseBlock = MakeBlock(m_blockName.c_str(), "_case_bb"); + m_postCaseBlock = MakeBlock(m_blockName.c_str(), "_post_case_bb"); + } + + // generate block for next case value in this case block + llvm::BasicBlock* nextBlock = MakeBlock(m_blockName.c_str(), "_next_case_bb"); + EvalCmp(m_switchValue, caseValue, JitExec::JIT_ICMP_EQ, m_caseBlock, nextBlock); + m_builder->SetInsertPoint(nextBlock); +} + +void JitSwitchCase::JitCaseManyTerm() +{ + MOT_ASSERT(m_caseBlock != nullptr); + MOT_ASSERT(m_postCaseBlock != nullptr); + MOT_ASSERT(!CurrentBlockEndsInBranch()); + + // if none of the values matches, we need to jump to next CASE clause + m_builder->CreateBr(m_postCaseBlock); + + // now start this CASE body + m_builder->SetInsertPoint(m_caseBlock); + OpenCompileFrame(m_codegenContext); + m_caseBlock = nullptr; +} + +void JitSwitchCase::JitBreak() +{ + // case handled, so we jump to code after the switch-case statement + if (!CurrentBlockEndsInBranch()) { + m_builder->CreateBr(m_postSwitchBlock); + } +} + +void JitSwitchCase::JitCaseEnd() +{ + // default label does not have a post case block + if (m_postCaseBlock != nullptr) { + // must be already set to null previously + MOT_ASSERT(m_caseBlock == nullptr); + m_builder->SetInsertPoint(m_postCaseBlock); + m_postCaseBlock = nullptr; + } +} + +void JitSwitchCase::JitDefault() +{ + MOT_ASSERT(!CurrentBlockEndsInBranch()); + m_builder->CreateBr(m_defaultCaseBlock); + m_builder->SetInsertPoint(m_defaultCaseBlock); + OpenCompileFrame(m_codegenContext); // open default-block compile frame +} + +void JitSwitchCase::JitEnd() +{ + // if last case statement did not call "break", we do it ourselves + if (!CurrentBlockEndsInBranch()) { + m_builder->CreateBr(m_postSwitchBlock); + } + m_builder->SetInsertPoint(m_postSwitchBlock); +} + +JitTryCatch::JitTryCatch(LlvmCodeGenContext* codegenContext, const char* blockName) + : JitStatement(codegenContext), m_blockName(blockName), m_ifTry(nullptr), m_exceptionSwitchCase(nullptr) +{ + m_beginTryBlock = MakeBlock(blockName, "_begin_try_bb"); + m_postTryCatchBlock = MakeBlock(blockName, "_post_try_catch_bb"); + + // terminate previous block and start a new one (at least for code readability) + m_builder->CreateBr(m_beginTryBlock); + m_builder->SetInsertPoint(m_beginTryBlock); + + // push frame and call setjmp intrinsic + llvm::Value* setJumpBuf = AddLlvmPushExceptionFrame(m_codegenContext); + LLVM_DEBUG_PRINT_FRAME(m_codegenContext, "Calling setjmp on frame", setJumpBuf); + llvm::Value* setJumpRes = AddLlvmSetJmp(m_codegenContext, setJumpBuf); + + // if result is zero, then this is normal execution, otherwise some exception was thrown + m_ifTry = new (std::nothrow) JitIf(m_codegenContext, (m_blockName + "_begin_try").c_str()); + if (m_ifTry == nullptr) { + MOT_LOG_TRACE("Failed to allocate JIT IF statement for TRY-CATCH"); + } else { + m_ifTry->JitIfNot(setJumpRes); + } + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitTryCatch()", "Starting normal flow"); + + // push this object on top of the try-catch-stack + m_next = TRY_CATCH_STACK; + TRY_CATCH_STACK = this; +} + +JitTryCatch::~JitTryCatch() +{ + // cleanup if needed + if (m_ifTry != nullptr) { + MOT_LOG_TRACE("Releasing dangling if in try/catch statement, missing catch handler?"); + delete m_ifTry; + m_ifTry = nullptr; + } + if (m_exceptionSwitchCase != nullptr) { + MOT_LOG_TRACE("Releasing dangling switch-case in try/catch statement, missing catch handler?"); + delete m_exceptionSwitchCase; + m_exceptionSwitchCase = nullptr; + } + // pop this object from the compile-time try-catch-stack + TRY_CATCH_STACK = TRY_CATCH_STACK->m_next; +} + +JitTryCatch* JitTryCatch::GetCurrentStatement() +{ + return TRY_CATCH_STACK; +} + +void JitTryCatch::JitThrow(llvm::Value* exceptionValue) +{ + // save fault code and raise exception status + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitThrow()", "Throwing exception"); + AddLlvmThrowException(m_codegenContext, exceptionValue); +} + +void JitTryCatch::JitRethrow() +{ + // raise exception status + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitRethrow()", "Re-throwing exception"); + AddLlvmRethrowException(m_codegenContext); +} + +void JitTryCatch::JitBeginCatch(llvm::Value* value) +{ + if (m_exceptionSwitchCase == nullptr) { + PrepareExceptionBlock(); + } + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitCase(value); + } + + // mark exception handled (but leave exception value register intact, to allow re-throw, it will be reset later) + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitBeginCatch()", "Entering single-value catch handler"); + AddLlvmResetExceptionStatus(m_codegenContext); +} + +void JitTryCatch::JitBeginCatchMany(llvm::Value* value) +{ + if (m_exceptionSwitchCase == nullptr) { + PrepareExceptionBlock(); + } + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitCaseMany(value); + } +} + +void JitTryCatch::JitCatchManyTerm() +{ + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitCaseManyTerm(); + } + + // mark exception handled (but leave exception value register intact, to allow re-throw, it will be reset later) + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEndCatchMany()", "Entering multi-value catch handler"); + AddLlvmResetExceptionStatus(m_codegenContext); +} + +void JitTryCatch::JitEndCatch() +{ + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEndCatch()", "Exiting catch handler"); + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitBreak(); + m_exceptionSwitchCase->JitCaseEnd(); + } +} + +void JitTryCatch::JitCatchAll() +{ + if (m_exceptionSwitchCase == nullptr) { + PrepareExceptionBlock(); + } + + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitDefault(); + } + + // mark exception handled (but leave exception value register intact, to allow re-throw, it will be reset later) + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitCatchAll()", "Entering catch-all handler"); + AddLlvmResetExceptionStatus(m_codegenContext); +} + +void JitTryCatch::JitEnd() +{ + // end switch-case block + if (m_exceptionSwitchCase != nullptr) { + m_exceptionSwitchCase->JitEnd(); + delete m_exceptionSwitchCase; + m_exceptionSwitchCase = nullptr; + } + + // end if block + MOT_ASSERT(m_ifTry); + if (m_ifTry != nullptr) { + m_ifTry->JitEnd(); + delete m_ifTry; + m_ifTry = nullptr; + } + + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEnd()", "Exiting try-catch block"); + // if exception not handled then re-throw + { + JitIf jitIf(m_codegenContext, "check_exception_not_handled"); + llvm::Value* exceptionStatus = AddLlvmGetExceptionStatus(m_codegenContext); + llvm::ConstantInt* statusValue = + llvm::ConstantInt::get(m_codegenContext->INT32_T, LLVM_EXCEPTION_STATUS_NONE, true); + jitIf.JitIfCmp(exceptionStatus, statusValue, JitExec::JIT_ICMP_NE); + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEnd()", "Exception unhandled, unwinding stack"); + AddLlvmUnwindExceptionFrame(m_codegenContext); + jitIf.JitElse(); + // if we reached here, it means that the exception was handled, so we clear the exception register + // pay attention that the exception status register is supposed to be already reset by some catch handler + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEnd()", "Exception handled, resetting value"); + AddLlvmResetExceptionValue(m_codegenContext); // clear off exception value register + jitIf.JitEnd(); + } + + // start exception post-processing + m_builder->CreateBr(m_postTryCatchBlock); + m_builder->SetInsertPoint(m_postTryCatchBlock); + LLVM_DEBUG_PRINT(m_codegenContext, "JitTryCatch::JitEnd()", "Starting post-try-catch block"); +} + +llvm::Value* JitTryCatch::JitGetExceptionStatus() +{ + return AddLlvmGetExceptionStatus(m_codegenContext); +} + +void JitTryCatch::PrepareExceptionBlock() +{ + // try block ended without exception being thrown, so jump to post block + if (!CurrentBlockEndsInBranch()) { + AddLlvmPopExceptionFrame(m_codegenContext); + m_builder->CreateBr(m_postTryCatchBlock); + } else { + // in case of return instruction, the SP code is fine (but not in general case...) + // in case of some other bizarre goto, the result is undefined. + // for now we do nothing, assuming caller is aware of the effect of his actions + + // although we should insert pop-exception frame instruction before the branch/return instruction + // in reality, the caller is responsible for popping all exception frames before calling return. + } + + // handle siglongjmp() result not zero - some exception was thrown + MOT_ASSERT(m_ifTry != nullptr); + if (m_ifTry != nullptr) { + m_ifTry->JitElse(); + } + LLVM_DEBUG_PRINT( + m_codegenContext, "JitTryCatch::PrepareExceptionBlock()", "Entering else block of exception handler"); + + // pop exception frame and get the exception value + llvm::Value* exceptionValue = AddLlvmPopExceptionFrame(m_codegenContext); + + // now begin handle exceptions + m_exceptionSwitchCase = + new (std::nothrow) JitSwitchCase(m_codegenContext, (m_blockName + "_catch").c_str(), exceptionValue); + if (m_exceptionSwitchCase == nullptr) { + MOT_LOG_TRACE("Failed to allocate JIT SWITCH-CASE statement for TRY-CATCH"); + } +} +} // namespace llvm_util diff --git a/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.h b/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.h index 8c640ddb6..40f8bd40a 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_llvm_util.h @@ -34,10 +34,261 @@ #include "codegen/gscodegen.h" #include "jit_common.h" +/** @define Access violation run-time fault (attempt to access null pointer, or array index out of bounds). */ +#define LLVM_FAULT_ACCESS_VIOLATION 1 + +/** @define Unhandled exception run-time fault (thrown exception not handled). */ +#define LLVM_FAULT_UNHANDLED_EXCEPTION 2 + +/** @define Resource limit reached during function execution. */ +#define LLVM_FAULT_RESOURCE_LIMIT 3 + +/*---------------------- Exception Codes -------------------*/ +/** @define Denotes that there is no exception presently being thrown or handled. */ +#define LLVM_EXCEPTION_STATUS_NONE 0 + +/** @define Denotes that an exception has just been thrown. */ +#define LLVM_EXCEPTION_STATUS_THROWN 1 + +/** @define Denotes that an exception handler is being searched for a thrown exception. */ +#define LLVM_EXCEPTION_STATUS_HANDLING 2 + // ATTENTION: In order to define all macros just once, we use the same class names. In order to avoid // name clashes, we include these classes in the llvm name space. Nevertheless, be careful not to // include this file and jit_util.h as well. -namespace llvm { +namespace llvm_util { +using namespace dorado; + +/** @brief Converts LLVM run-time fault code to string.*/ +extern const char* LlvmRuntimeFaultToString(int faultCode); + +/** @class RuntimeFaultHandler An interface for handling run-time faults at the function level. */ +class LlvmRuntimeFaultHandler { +public: + /** + * @brief Handles a run-time fault. + * @param faultCode The fault code. + */ + virtual void OnRuntimeFault(uint64_t faultCode) = 0; + +protected: + /** @brief Constructor. */ + LlvmRuntimeFaultHandler() + {} + + /** @brief Destructor. */ + virtual ~LlvmRuntimeFaultHandler() + {} +}; + +/** @class CompileFrame. */ +class CompileFrame { +public: + /** @brief Constructor. */ + explicit CompileFrame(CompileFrame* next) : m_next(next) + {} + + /** @brief Destructor. */ + ~CompileFrame() + {} + + inline CompileFrame* GetNext() + { + return m_next; + } + + /** + * @brief Queries whether the compile frame already contains a local variable by the given name. + * @param name The local variable name. + * @return True if this compile frame contains a local variable by the given name, otherwise false. + */ + inline bool ContainsLocalVariable(const char* name) + { + return m_localVarMap.find(name) != m_localVarMap.end(); + } + + inline const char* GetUniqueName() + { + char buf[MAX_VAR_NAME_LEN]; + errno_t erc = snprintf_s(buf, MAX_VAR_NAME_LEN, MAX_VAR_NAME_LEN - 1, "unnamed_%u", m_nextVarId++); + securec_check_ss(erc, "\0", "\0"); + m_uniqueVarName = buf; + return m_uniqueVarName.c_str(); + } + + /** + * @brief Defines local variable on the local frame. + * @param localVar The local variable to define. + * @return True if succeeded, otherwise false (if frame already contains local variable with the same name). + */ + bool AddLocalVariable(const char* name, llvm::Value* localVar) + { + return m_localVarMap.insert(LocalVarMap::value_type(name, localVar)).second; + } + + /** + * @brief Searches a local variable in the frame. + * @param name The name of the local variable. + * @return The local variable, or null if not found. + */ + llvm::Value* GetLocalVariable(const char* name) + { + llvm::Value* result = nullptr; + LocalVarMap::iterator itr = m_localVarMap.find(name); + if (itr != m_localVarMap.end()) { + result = itr->second; + } + return result; + } + +private: + static constexpr uint32_t MAX_VAR_NAME_LEN = 32; + + CompileFrame* m_next; + + /** @typedef Local variable map type. */ + using LocalVarMap = std::map; + + /** @var Local variable map. */ + LocalVarMap m_localVarMap; + + /** @var Buffer for next unique variable name. */ + std::string m_uniqueVarName; + + /** @var Identifier for next unique variable name. */ + static uint32_t m_nextVarId; +}; + +/** @struct Utility structure for LLVM code-generation. */ +struct LlvmCodeGenContext { + /** @var The Gauss code-generation context. */ + GsCodeGen* m_codeGen; + + /** @var The LLVM builder. */ + GsCodeGen::LlvmBuilder* m_builder; + + /** @var The currently built function. */ + llvm::Function* m_jittedFunction; + + // primitive types + llvm::IntegerType* BOOL_T; + llvm::IntegerType* INT1_T; // for IR assembly "select" + llvm::IntegerType* INT8_T; + llvm::IntegerType* INT16_T; + llvm::IntegerType* INT32_T; + llvm::IntegerType* INT64_T; + llvm::Type* VOID_T; + llvm::Type* FLOAT_T; + llvm::Type* DOUBLE_T; + llvm::PointerType* STR_T; + llvm::IntegerType* DATUM_T; + + // helper functions required to support LLVM simple exceptions + llvm::FunctionCallee m_llvmPushExceptionFrameFunc; + llvm::FunctionCallee m_llvmGetCurrentExceptionFrameFunc; + llvm::FunctionCallee m_llvmPopExceptionFrameFunc; + llvm::FunctionCallee m_llvmThrowExceptionFunc; + llvm::FunctionCallee m_llvmRethrowExceptionFunc; + llvm::FunctionCallee m_llvmGetExceptionValueFunc; + llvm::FunctionCallee m_llvmGetExceptionStatusFunc; + llvm::FunctionCallee m_llvmResetExceptionStatusFunc; + llvm::FunctionCallee m_llvmResetExceptionValueFunc; + llvm::FunctionCallee m_llvmUnwindExceptionFrameFunc; + llvm::FunctionCallee m_llvmSetJmpFunc; + llvm::FunctionCallee m_llvmLongJmpFunc; +#ifdef MOT_JIT_DEBUG + llvm::FunctionCallee m_llvmDebugPrintFunc; + llvm::FunctionCallee m_llvmDebugPrintFrameFunc; +#endif + + // invoke built-ins + llvm::FunctionCallee m_invokePGFunction0Func; + llvm::FunctionCallee m_invokePGFunction1Func; + llvm::FunctionCallee m_invokePGFunction2Func; + llvm::FunctionCallee m_invokePGFunction3Func; + llvm::FunctionCallee m_invokePGFunctionNFunc; + + // invoke MOT memory allocation + llvm::FunctionCallee m_memSessionAllocFunc; + llvm::FunctionCallee m_memSessionFreeFunc; + + /** @var The current compile-time frame. */ + CompileFrame* m_currentCompileFrame; +}; + +extern void OpenCompileFrame(LlvmCodeGenContext* ctx); + +extern void CloseCompileFrame(LlvmCodeGenContext* ctx); + +extern void CleanupCompileFrames(LlvmCodeGenContext* ctx); + +/** + * @brief Helper function for defining LLVM external function. + * @param module The LLVM module into which the function declaration is to be added. + * @param returnType The return type of the function. + * @param name The name of the function (must match the actual function name, otherwise linking fails). + * @param ... Function parameter list. List must be terminated with NULL. + * @return The resulting function definition. + */ +extern llvm::FunctionCallee DefineFunction(llvm::Module* module, llvm::Type* returnType, const char* name, ...); + +/** + * @brief Adds a function call. + * @param ctx The code-generation context. + * @param func The invoked function definition. + * @param ... Function argument list. List must be terminated with NULL. + * @return The return value of the function. + */ +extern llvm::Value* AddFunctionCall(LlvmCodeGenContext* ctx, llvm::FunctionCallee func, ...); + +/** + * @brief Adds a function call at the specified position. + * @param before The instruction before which the function call is to be added. + * @param ctx The code-generation context. + * @param func The invoked function definition. + * @param ... Function argument list. List must be terminated with NULL. + * @return The return value of the function. + */ +extern llvm::Value* InsertFunctionCall( + llvm::Instruction* before, LlvmCodeGenContext* ctx, llvm::FunctionCallee func, ...); + +/** @brief Adds invocation of PG function with no arguments. */ +extern llvm::Value* AddInvokePGFunction0(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId); + +/** @brief Adds invocation of PG function with 1 argument. */ +extern llvm::Value* AddInvokePGFunction1(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg, llvm::Value* isNull, Oid argType); + +/** @brief Adds invocation of PG function with 2 arguments. */ +extern llvm::Value* AddInvokePGFunction2(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg1, llvm::Value* isNull1, Oid argType1, llvm::Value* arg2, llvm::Value* isNull2, Oid argType2); + +/** @brief Adds invocation of PG function with 3 arguments. */ +extern llvm::Value* AddInvokePGFunction3(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value* arg1, llvm::Value* isNull1, Oid argType1, llvm::Value* arg2, llvm::Value* isNull2, Oid argType2, + llvm::Value* arg3, llvm::Value* isNull3, Oid argType3); + +/** @brief Adds invocation of PG function with 4 or more arguments. */ +extern llvm::Value* AddInvokePGFunctionN(LlvmCodeGenContext* ctx, PGFunction funcPtr, Oid collationId, bool isStrict, + llvm::Value** args, llvm::Value** isNull, Oid* argTypes, int argCount); + +/** @brief Adds call to allocate session memory. */ +extern llvm::Value* AddMemSessionAlloc(LlvmCodeGenContext* ctx, llvm::Value* allocSize); + +/** @brief Adds call to free session memory. */ +extern void AddMemSessionFree(LlvmCodeGenContext* ctx, llvm::Value* ptr); + +/** + * @brief Initializes a code-generation context. + * @param ctx The context object to initialize. + * @param codeGen The GaussDB code-generation object for LLVM. + * @param builder The LLVM builder. + */ +extern void InitLlvmCodeGenContext(LlvmCodeGenContext* ctx, GsCodeGen* codeGen, GsCodeGen::LlvmBuilder* builder); + +/** @brief Destroys a code-generation context. */ +extern void DestroyLlvmCodeGenContext(LlvmCodeGenContext* ctx); + /** @class Static helpers. */ class JitUtils { public: @@ -60,7 +311,7 @@ public: * @return The comparison result (1 if comparison is true, otherwise 0). */ static llvm::Value* ExecCompare( - dorado::GsCodeGen::LlvmBuilder* builder, llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpOp cmpOp); + GsCodeGen::LlvmBuilder* builder, llvm::Value* lhs, llvm::Value* rhs, JitExec::JitICmpOp cmpOp); /** * @brief Constructs a typed zero value by another value. @@ -83,22 +334,32 @@ private: /** @class Implements a generic statement. */ class JitStatement { protected: + /** @var The code-generation context. */ + LlvmCodeGenContext* m_codegenContext; + /** @var The LLVM context. */ llvm::LLVMContext& m_context; /** @var The LLVM builder. */ - dorado::GsCodeGen::LlvmBuilder* m_builder; + GsCodeGen::LlvmBuilder* m_builder; /** * @brief Constructor. - * @param context The LLVM context. - * @param builder The LLVM builder. + * @param codegenContext The code-generation context. */ - JitStatement(llvm::LLVMContext& context, dorado::GsCodeGen::LlvmBuilder* builder); + explicit JitStatement(LlvmCodeGenContext* codegenContext); /** @brief Destructor. */ virtual ~JitStatement(); + /** + * @brief Creates an instruction block/ + * @param blockBaseName The base-name of the block. + * @param suffix The suffix to add to the block name. + * @return The created block, or null if failed. + */ + llvm::BasicBlock* MakeBlock(const char* blockBaseName, const char* suffix); + /** * @brief Compares two values. If the values pass the condition then the success-block begins, * otherwise the fail block begins. @@ -141,13 +402,10 @@ class JitIf : public JitStatement { public: /** * @brief Constructor. - * @param context The LLVM context. - * @param builder The LLVM builder. - * @param jittedFunction The jitted function. + * @param codegenContext The code-generation context. * @param blockName The if-block name. */ - JitIf(llvm::LLVMContext& context, dorado::GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, - const char* blockName); + JitIf(LlvmCodeGenContext* codegenContext, const char* blockName); /** @brief Destructor. */ ~JitIf() override; @@ -184,12 +442,6 @@ public: /** @brief Terminates the if block. Calling this function is mandatory. */ void JitEnd(); - /** @brief Retrieves the condition evaluation block. */ - inline llvm::BasicBlock* GetCondBlock() - { - return m_condBlock; - }; - /** @brief Retrieves the condition success execution block. */ inline llvm::BasicBlock* GetIfBlock() { @@ -209,7 +461,6 @@ public: }; private: - llvm::BasicBlock* m_condBlock; llvm::BasicBlock* m_ifBlock; llvm::BasicBlock* m_elseBlock; llvm::BasicBlock* m_postIfBlock; @@ -219,18 +470,58 @@ private: JitIf* m_next; }; +class JitLoop : public JitStatement { +public: + /** @brief Retrieves the name of the loop. */ + inline const char* GetName() const + { + return m_name.c_str(); + } + + /** @brief Retrieves the current (innermost if nested) loop. */ + static JitLoop* GetCurrentStatement(); + + /** @brief Stops the current iteration and begins a new one. */ + virtual void JitContinue() = 0; + + /** @brief Breaks out of the while loop. */ + virtual void JitBreak() = 0; + + /** @brief Retrieves the condition evaluation block of the loop. */ + virtual llvm::BasicBlock* GetCondBlock() = 0; + + /** @brief Retrieves the post-loop execution block. */ + virtual llvm::BasicBlock* GetPostBlock() = 0; + +protected: + /** + * @brief Constructor. + * @param codegenContext The code-generation context. + * @param name The loop name. Be careful not to give duplicate names to different loops in the same chain of + * nested loops (causes debug-build assert). + */ + JitLoop(LlvmCodeGenContext* codegenContext, const char* name); + + /** @brief Destructor. */ + ~JitLoop() override; + +private: + /** @brief The loop name. */ + std::string m_name; + + /** @brief The next loop-block (outer with respect to this block). */ + JitLoop* m_next; +}; + /** @class Implements a while construct. */ -class JitWhile : public JitStatement { +class JitWhile : public JitLoop { public: /** * @brief Constructor. - * @param context The LLVM context. - * @param builder The LLVM builder. - * @param jittedFunction The jitted function. + * @param codegenContext The code-generation context. * @param blockName The if-block name. */ - JitWhile(llvm::LLVMContext& context, dorado::GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, - const char* blockName); + JitWhile(LlvmCodeGenContext* codegenContext, const char* blockName); /** @brief Destructor. */ ~JitWhile() override; @@ -262,10 +553,10 @@ public: void JitWhileNot(llvm::Value* value); /** @brief Stops the current iteration and begins a new one. */ - void JitContinue(); + void JitContinue() override; /** @brief Breaks out of the while loop. */ - void JitBreak(); + void JitBreak() override; /** @brief Terminates the while block. Calling this function is mandatory. */ void JitEnd(); @@ -274,19 +565,19 @@ public: inline llvm::BasicBlock* GetCondBlock() { return m_condWhileBlock; - }; + } /** @brief Retrieves the condition success execution block. */ inline llvm::BasicBlock* GetExecBlock() { return m_execWhileBlock; - }; + } /** @brief Retrieves the post-if execution block. */ inline llvm::BasicBlock* GetPostBlock() { return m_postWhileBlock; - }; + } private: llvm::BasicBlock* m_condWhileBlock; @@ -298,17 +589,14 @@ private: }; /** @class Implements a do-while construct. */ -class JitDoWhile : public JitStatement { +class JitDoWhile : public JitLoop { public: /** * @brief Constructor. - * @param context The LLVM context. - * @param builder The LLVM builder. - * @param jittedFunction The jitted function. + * @param codegenContext The code-generation context. * @param blockName The if-block name. */ - JitDoWhile(llvm::LLVMContext& context, dorado::GsCodeGen::LlvmBuilder* builder, llvm::Function* jittedFunction, - const char* blockName); + JitDoWhile(LlvmCodeGenContext* codegenContext, const char* blockName); /** @brief Destructor. */ ~JitDoWhile() override; @@ -317,10 +605,10 @@ public: static JitDoWhile* GetCurrentStatement(); /** @brief Stops the current iteration and begins a new one by first evaluating the condition. */ - void JitContinue(); + void JitContinue() override; /** @brief Breaks out of the do-while loop. */ - void JitBreak(); + void JitBreak() override; /** * @brief Marks the beginning of the test condition block. This is followed by code to compute the @@ -380,25 +668,266 @@ private: /** @brief The next do-while-block (outer with respect to this block). */ JitDoWhile* m_next; }; -} // namespace llvm + +/** @class Implements a for-loop construct. */ +class JitFor : public JitLoop { +public: + /** + * @brief Constructor. + * @param codegenContext The code-generation context. + * @param blockName The for-loop block name. + * @param counterName The loop counter name (type is derived from init_value). + * @param initValue The initial counter value. + * @param step The increment step of the loop counter. + * @param bound The loop counter bound. + * @param cmpOp The comparison operation to use when comparing the loop counter with the loop bound. + */ + JitFor(LlvmCodeGenContext* codegenContext, const char* blockName, const char* counterName, llvm::Value* initValue, + llvm::Value* step, llvm::Value* bound, JitExec::JitICmpOp cmpOp); + + /** + * @brief Constructor. + * @param codegenContext The code-generation context. + * @param blockName The for-loop block name. + * @param counterVar The loop counter variable, externally provided by the caller (type should match type of + * initValue). + * @param initValue The initial counter value. + * @param step The increment step of the loop counter. + * @param bound The loop counter bound. + * @param cmpOp The comparison operation to use when comparing the loop counter with the loop bound. + */ + JitFor(LlvmCodeGenContext* codegenContext, const char* blockName, llvm::Value* counterVar, llvm::Value* initValue, + llvm::Value* step, llvm::Value* bound, JitExec::JitICmpOp cmpcmpOp_op); + + /** @brief Destructor. */ + ~JitFor() override; + + /** @brief Retrieves the current (innermost if nested) for-loop-block. */ + static JitFor* GetCurrentStatement(); + + /** @brief Retrieves the counter value. */ + llvm::Value* GetCounter(); + + /** + * @brief Stops the current iteration and begins a new one by incrementing the loop counter and then evaluating + * the loop condition. + */ + void JitContinue() override; + + /** @brief Breaks out of the for loop. */ + void JitBreak() override; + + /** @brief Terminates the do-while block. Calling this function is mandatory. */ + void JitEnd(); + + /** @brief Retrieves the counter initialization block. */ + inline llvm::BasicBlock* GetInitBlock() + { + return m_initForCounterBlock; + } + + /** @brief Retrieves the condition evaluation block. */ + llvm::BasicBlock* GetCondBlock() override + { + return m_condForBlock; + } + + /** @brief Retrieves the for-loop body execution block. */ + inline llvm::BasicBlock* GetExecBlock() + { + return m_execForBodyBlock; + } + + /** @brief Retrieves the counter increment block. */ + inline llvm::BasicBlock* GetIncrementBlock() + { + return m_incrementForCounterBlock; + } + + /** @brief Retrieves the post-for execution block. */ + llvm::BasicBlock* GetPostBlock() override + { + return m_postForBlock; + } + +private: + llvm::BasicBlock* m_initForCounterBlock; + llvm::BasicBlock* m_condForBlock; + llvm::BasicBlock* m_execForBodyBlock; + llvm::BasicBlock* m_incrementForCounterBlock; + llvm::BasicBlock* m_postForBlock; + + llvm::Value* m_counterValue; + llvm::Value* m_step; + llvm::Value* m_bound; + JitExec::JitICmpOp m_cmpOp; + + /** @brief The next for-loop-block (outer with respect to this block). */ + JitFor* m_next; + + void Init(const char* blockName, const char* counterName, llvm::Value* counterVar, llvm::Value* initValue); +}; + +/** @class Implements a switch-case construct. */ +class JitSwitchCase : public JitStatement { +public: + /** + * @brief Constructor. + * @param codegenContext The code-generation context. + * @param blockName The do-while-block name. + * @param switchValue The tested value. + */ + JitSwitchCase(LlvmCodeGenContext* codegenContext, const char* blockName, llvm::Value* switchValue); + + /** @brief Destructor. */ + ~JitSwitchCase() override; + + /** @brief Retrieves the current (innermost if nested) for-loop-block. */ + static JitSwitchCase* GetCurrentStatement(); + + /** @brief Starts a switch-case-block. */ + void JitCase(llvm::Value* caseValue); + + /** @brief Adds a case value to a switch-case block with multiple values (batch a few cases together). */ + void JitCaseMany(llvm::Value* caseValue); + + /** @brief Ends a switch-case block with multiple values. */ + void JitCaseManyTerm(); + + /** @brief Terminates a switch-case block. */ + void JitBreak(); + + /** @brief Ends a case block. */ + void JitCaseEnd(); + + /** @brief Starts the default switch-case-block. */ + void JitDefault(); + + /** @brief Terminates the switch-case statement. Calling this function is mandatory. */ + void JitEnd(); + + /** @brief Retrieves the counter initialization block. */ + inline llvm::BasicBlock* GetDefaultCaseBlock() + { + return m_defaultCaseBlock; + } + + /** @brief Retrieves the post-for execution block. */ + inline llvm::BasicBlock* GetPostBlock() + { + return m_postSwitchBlock; + } + +private: + std::string m_blockName; + llvm::Value* m_switchValue; + llvm::BasicBlock* m_caseBlock; + llvm::BasicBlock* m_postCaseBlock; + llvm::BasicBlock* m_defaultCaseBlock; + llvm::BasicBlock* m_postSwitchBlock; + + /** @brief The next switch-case-block (outer with respect to this block). */ + JitSwitchCase* m_next; +}; + +/** @class Implements a try-catch construct. */ +class JitTryCatch : public JitStatement { +public: + /** + * @brief Constructor. + * @param codegenContext The code-generation context. + * @param blockName The try-catch-block name. + */ + JitTryCatch(LlvmCodeGenContext* codegenContext, const char* blockName); + + /** @brief Destructor. */ + ~JitTryCatch() override; + + /** @brief Retrieves the current (innermost if nested) try-catch-block. */ + static JitTryCatch* GetCurrentStatement(); + + /** @brief Throws a value. */ + void JitThrow(llvm::Value* exceptionValue); + + /** @brief Re-throws the recent exception value (use only inside catch block). */ + void JitRethrow(); + + /** @brief Begins a catch block. */ + void JitBeginCatch(llvm::Value* value); + + /** + * @brief Begins a catch block of multiple values. Can be called several times with multiple values. When all case + * values are specified, a call to @ref JitTryCatch::JitEndCatchMany() should be made (after which the case body + * should be entered. This call should be finally matched with a call to @ref JitTryCatch::JitEndCatch(). + */ + void JitBeginCatchMany(llvm::Value* value); + + /** @brief Ends a "catch multiple values" statement and begins the catch body block. */ + void JitCatchManyTerm(); + + /** @brief Terminates a catch block. */ + void JitEndCatch(); + + /** @brief Starts the catch-all block. */ + void JitCatchAll(); + + /** @brief Terminates the try-catch statement. Calling this function is mandatory. */ + void JitEnd(); + + /** @brief Retrieves the post-for execution block. */ + inline llvm::BasicBlock* GetPostBlock() + { + return m_postTryCatchBlock; + } + + /** @brief Queries whether there is a pending exception. */ + llvm::Value* JitGetExceptionStatus(); + +private: + std::string m_blockName; + JitIf* m_ifTry; + JitSwitchCase* m_exceptionSwitchCase; + llvm::BasicBlock* m_beginTryBlock; + llvm::BasicBlock* m_postTryCatchBlock; + + /** @brief The next try-catch-block (outer with respect to this block). */ + JitTryCatch* m_next; + + /** @brief Make preparations for exception block. */ + void PrepareExceptionBlock(); +}; +} // namespace llvm_util /************************************** - * Pseudo return Statement + * Return Statement **************************************/ /** @define Helper macro for a constant instruction. */ -#define JIT_CONST(value) llvm::ConstantInt::get(ctx->INT32_T, (value), true) +#define JIT_CONST_CHAR(value) llvm::ConstantInt::get(ctx->INT8_T, (value), true) +#define JIT_CONST_BOOL(value) llvm::ConstantInt::get(ctx->BOOL_T, (value), true) +#define JIT_CONST_INT1(value) llvm::ConstantInt::get(ctx->INT1_T, (value), true) +#define JIT_CONST_INT8(value) llvm::ConstantInt::get(ctx->INT8_T, (value), true) +#define JIT_CONST_INT16(value) llvm::ConstantInt::get(ctx->INT16_T, (value), true) +#define JIT_CONST_INT32(value) llvm::ConstantInt::get(ctx->INT32_T, (value), true) +#define JIT_CONST_INT64(value) llvm::ConstantInt::get(ctx->INT64_T, (value), true) +#define JIT_CONST_UINT8(value) llvm::ConstantInt::get(ctx->INT8_T, (value), false) +#define JIT_CONST_UINT16(value) llvm::ConstantInt::get(ctx->INT16_T, (value), false) +#define JIT_CONST_UINT32(value) llvm::ConstantInt::get(ctx->INT32_T, (value), false) +#define JIT_CONST_UINT64(value) llvm::ConstantInt::get(ctx->INT64_T, (value), false) +#define JIT_CONST_FLOAT(value) llvm::ConstantFP::get(ctx->FLOAT_T, (value), true) +#define JIT_CONST_DOUBLE(value) llvm::ConstantFP::get(ctx->DOUBLE_T, (value), true) /** @define Helper macro for a return statement. */ -#define JIT_RETURN(value) ctx->_builder->CreateRet(value) +#define JIT_RETURN(value) ctx->m_builder->CreateRet(value) -/** @define Helper macro for a return constant statement. */ -#define JIT_RETURN_CONST(value) ctx->_builder->CreateRet(JIT_CONST(value)) +/** @define Helper macro for defining a basic block. */ +#define JIT_DEFINE_BLOCK(blockName) \ + llvm::BasicBlock* blockName = llvm::BasicBlock::Create(ctx->m_codeGen->context(), #blockName) /** @define Helper macro for a branch/goto statement. */ -#define JIT_GOTO(block) ctx->_builder->CreateBr(block) +#define JIT_GOTO(block) ctx->m_builder->CreateBr(block) /** @define Macro for getting the current block. */ -#define JIT_CURRENT_BLOCK() ctx->_builder->GetInsertBlock() +#define JIT_CURRENT_BLOCK() ctx->m_builder->GetInsertBlock() /************************************** * If Statement @@ -409,10 +938,15 @@ private: */ #define JIT_IF_BEGIN(name) \ { \ - llvm::JitIf jitIf##name(ctx->_code_gen->context(), ctx->_builder, ctx->m_jittedQuery, #name); + llvm_util::JitIf jitIf(ctx, #name); + +/** @define Macro similar to @ref JIT_FOR_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_IF_BEGIN_V(name) \ + { \ + llvm_util::JitIf jitIf(ctx, name); /** @define Macro for referring the current if-block. */ -#define JIT_IF_CURRENT() llvm::JitIf::GetCurrentStatement() +#define JIT_IF_CURRENT() llvm_util::JitIf::GetCurrentStatement() /************************************** * While Statement @@ -420,10 +954,15 @@ private: /** @define Macro for starting a native while statement. */ #define JIT_WHILE_BEGIN(name) \ { \ - llvm::JitWhile jitWhile##name(ctx->_code_gen->context(), ctx->_builder, ctx->m_jittedQuery, #name); + llvm_util::JitWhile jitWhile(ctx, #name); + +/** @define Macro similar to @ref JIT_WHILE_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_WHILE_BEGIN_V(name) \ + { \ + llvm_util::JitWhile jitWhile(ctx, name); /** @define Macro for referring the current while-block. */ -#define JIT_WHILE_CURRENT() llvm::JitWhile::GetCurrentStatement() +#define JIT_WHILE_CURRENT() llvm_util::JitWhile::GetCurrentStatement() /************************************** * Do-While Statement @@ -431,10 +970,87 @@ private: /** @define Macro for starting a native do-while statement. */ #define JIT_DO_WHILE_BEGIN(name) \ { \ - llvm::JitDoWhile jitDoWhile##name(ctx->_code_gen->context(), ctx->_builder, ctx->m_jittedQuery, #name); + llvm::JitDoWhile jitDoWhile(ctx, #name); + +/** @define Macro similar to @ref JIT_DO_WHILE_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_DO_WHILE_BEGIN_V(name) \ + { \ + llvm_util::JitDoWhile jitDoWhile(ctx, name); /** @define Macro for referring the current do-while-block. */ -#define JIT_DO_WHILE_CURRENT() llvm::JitDoWhile::GetCurrentStatement() +#define JIT_DO_WHILE_CURRENT() llvm_util::JitDoWhile::GetCurrentStatement() + +/************************************** + * For Statement + **************************************/ + +/** @define Macro for starting a LLVM for statement. */ +#define JIT_FOR_BEGIN(name, counterName, initValue, step, bound, cmpOp) \ + { \ + llvm_util::JitFor jitFor(ctx, #name, #counterName, initValue, step, bound, cmpOp); + +/** @define Macro similar to @ref JIT_FOR_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_FOR_BEGIN_V(name, counterName, initValue, step, bound, cmpOp) \ + { \ + llvm_util::JitFor jitFor(ctx, name, counterName, initValue, step, bound, cmpOp); + +/** @define Macro similar to @ref JIT_FOR_BEGIN(), but with pointer to counter variable. */ +#define JIT_FOR_BEGIN_X(name, counterVar, initValue, step, bound, cmpOp) \ + { \ + llvm_util::JitFor jitFor(ctx, #name, counterVar, initValue, step, bound, cmpOp); + +/** @define Macro for referring the current for-loop-block. */ +#define JIT_FOR_CURRENT() llvm_util::JitFor::GetCurrentStatement() + +/** @define Macro for getting the condition evaluation block of the current for statement. */ +#define JIT_FOR_INCREMENT_BLOCK() JIT_FOR_CURRENT()->GetIncrementBlock() + +/** @define Macro for getting the block after the current for statement. */ +#define JIT_FOR_POST_BLOCK() JIT_FOR_CURRENT()->GetPostBlock() + +/************************************** + * Loop Macros + **************************************/ + +/** @define Macro for getting the current (innermost if nested) loop. */ +#define JIT_LOOP_CURRENT() llvm_util::JitLoop::GetCurrentStatement() + +/************************************** + * Switch-Case Statement + **************************************/ + +/** @define Macro for starting a LLVM for statement. */ +#define JIT_SWITCH_BEGIN(name, value) \ + { \ + llvm_util::JitSwitchCase jitSwitchCase(ctx, #name, value); + +/** @define Macro similar to @ref JIT_SWITCH_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_SWITCH_BEGIN_V(name, value) \ + { \ + llvm_util::JitSwitchCase jitSwitchCase(ctx, name, value); + +/** @define Macro for referring the current switch-case-block. */ +#define JIT_SWITCH_CURRENT() llvm_util::JitSwitchCase::GetCurrentStatement() + +/************************************** + * Try-Catch Statement + **************************************/ + +/** @define Macro for starting a LLVM for statement. */ +#define JIT_TRY_BEGIN(name) \ + { \ + llvm_util::JitTryCatch jitTryCatch((llvm_util::LlvmCodeGenContext*)(ctx), #name); + +/** @define Macro similar to @ref JIT_TRY_BEGIN(), but with string values (either quoted constant or pointer). */ +#define JIT_TRY_BEGIN_V(name) \ + { \ + llvm_util::JitTryCatch jitTryCatch(ctx, name); + +/** @define Macro for referring the current switch-case-block. */ +#define JIT_TRY_CURRENT() llvm_util::JitTryCatch::GetCurrentStatement() + +/** @define Helper for managing exceptions signaled in helper functions. */ +#define JIT_EXCEPTION_STATUS() JIT_TRY_CURRENT()->JitGetExceptionStatus() // the other macros for emitting JIT code are found in jit_util.h // make sure to include that file after including this file diff --git a/src/gausskernel/storage/mot/jit_exec/jit_pgproc.h b/src/gausskernel/storage/mot/jit_exec/jit_pgproc.h deleted file mode 100644 index 4fc681f24..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_pgproc.h +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_pgproc.h - * JIT PGPROC Operators. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_pgproc.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_PGPROC_H -#define JIT_PGPROC_H - -#include "utils/builtins.h" -#include "utils/date.h" -#include "utils/int8.h" - -/*--------------------------- PGPROC Operator ---------------------------*/ -// all definitions taken from the following files: -// src/backend/utils/adt/bool.cpp -// src/backend/utils/adt/char.cpp -// src/backend/utils/adt/int.cpp -// src/backend/utils/adt/int8.cpp -// src/backend/utils/adt/date.cpp -// src/backend/utils/adt/varchar.cpp -// src/backend/utils/adt/varlena.cpp -// src/backend/utils/adt/float.cpp -// src/backend/utils/adt/numeric.cpp -/** - * @define Utility macro for making definitions that apply on all supported operators. - * @detail The macro should be used as follows: - * @code{.cpp} - * #define APPLY_UNARY_OPERATOR(funcid, name) ...; - * #define APPLY_BINARY_OPERATOR(funcid, name) ...; - * #define APPLY_TERNARY_OPERATOR(funcid, name) ...; - * #define APPLY_UNARY_CAST_OPERATOR(funcid, name) ...; - * #define APPLY_BINARY_CAST_OPERATOR(funcid, name) ...; - * #define APPLY_TERNARY_CAST_OPERATOR(funcid, name) ...; - * - * APPLY_OPERATORS() - * - * #undef APPLY_UNARY_OPERATOR - * #undef APPLY_BINARY_OPERATOR - * #undef APPLY_TERNARY_OPERATOR - * #undef APPLY_UNARY_CAST_OPERATOR - * #undef APPLY_BINARY_CAST_OPERATOR - * #undef APPLY_TERNARY_CAST_OPERATOR - * @endcode - */ -#define APPLY_OPERATORS() \ - APPLY_UNARY_OPERATOR(CHARTOBPCHARFUNCOID, char_bpchar) \ - APPLY_BINARY_OPERATOR(60, booleq) \ - APPLY_BINARY_OPERATOR(61, chareq) \ - APPLY_UNARY_CAST_OPERATOR(77, chartoi4) \ - APPLY_UNARY_CAST_OPERATOR(78, i4tochar) \ - APPLY_UNARY_CAST_OPERATOR(313, i2toi4) \ - APPLY_UNARY_CAST_OPERATOR(314, i4toi2) \ - APPLY_UNARY_CAST_OPERATOR(2557, int4_bool) \ - APPLY_UNARY_CAST_OPERATOR(2558, bool_int4) \ - APPLY_UNARY_CAST_OPERATOR(3180, int2_bool) \ - APPLY_UNARY_CAST_OPERATOR(3181, bool_int2) \ - APPLY_BINARY_OPERATOR(65, int4eq) \ - APPLY_BINARY_OPERATOR(63, int2eq) \ - APPLY_BINARY_OPERATOR(158, int24eq) \ - APPLY_BINARY_OPERATOR(159, int42eq) \ - APPLY_UNARY_OPERATOR(212, int4um) \ - APPLY_UNARY_OPERATOR(1912, int4up) \ - APPLY_BINARY_OPERATOR(177, int4pl) \ - APPLY_BINARY_OPERATOR(181, int4mi) \ - APPLY_BINARY_OPERATOR(141, int4mul) \ - APPLY_BINARY_OPERATOR(154, int4div) \ - APPLY_UNARY_OPERATOR(766, int4inc) \ - APPLY_UNARY_OPERATOR(213, int2um) \ - APPLY_UNARY_OPERATOR(1911, int2up) \ - APPLY_BINARY_OPERATOR(176, int2pl) \ - APPLY_BINARY_OPERATOR(180, int2mi) \ - APPLY_BINARY_OPERATOR(152, int2mul) \ - APPLY_BINARY_OPERATOR(153, int2div) \ - APPLY_BINARY_OPERATOR(178, int24pl) \ - APPLY_BINARY_OPERATOR(182, int24mi) \ - APPLY_BINARY_OPERATOR(170, int24mul) \ - APPLY_BINARY_OPERATOR(172, int24div) \ - APPLY_BINARY_OPERATOR(179, int42pl) \ - APPLY_BINARY_OPERATOR(183, int42mi) \ - APPLY_BINARY_OPERATOR(171, int42mul) \ - APPLY_BINARY_OPERATOR(173, int42div) \ - APPLY_BINARY_OPERATOR(156, int4mod) \ - APPLY_BINARY_OPERATOR(155, int2mod) \ - APPLY_UNARY_OPERATOR(1251, int4abs) \ - APPLY_UNARY_OPERATOR(1253, int2abs) \ - APPLY_BINARY_OPERATOR(1898, int4and) \ - APPLY_BINARY_OPERATOR(1899, int4or) \ - APPLY_BINARY_OPERATOR(1900, int4xor) \ - APPLY_BINARY_OPERATOR(1902, int4shl) \ - APPLY_BINARY_OPERATOR(1903, int4shr) \ - APPLY_UNARY_OPERATOR(1901, int4not) \ - APPLY_BINARY_OPERATOR(1892, int2and) \ - APPLY_BINARY_OPERATOR(1893, int2or) \ - APPLY_BINARY_OPERATOR(1894, int2xor) \ - APPLY_BINARY_OPERATOR(1896, int2shl) \ - APPLY_BINARY_OPERATOR(1897, int2shr) \ - APPLY_UNARY_OPERATOR(1895, int2not) \ - APPLY_BINARY_OPERATOR(6101, int1and) \ - APPLY_BINARY_OPERATOR(6102, int1or) \ - APPLY_BINARY_OPERATOR(6103, int1xor) \ - APPLY_BINARY_OPERATOR(6105, int1shl) \ - APPLY_BINARY_OPERATOR(6106, int1shr) \ - APPLY_UNARY_OPERATOR(6104, int1not) \ - APPLY_BINARY_OPERATOR(5547, int1eq) \ - APPLY_UNARY_CAST_OPERATOR(5523, i1toi2) \ - APPLY_UNARY_CAST_OPERATOR(5524, i2toi1) \ - APPLY_UNARY_CAST_OPERATOR(5525, i1toi4) \ - APPLY_UNARY_CAST_OPERATOR(5526, i4toi1) \ - APPLY_UNARY_CAST_OPERATOR(5527, i1toi8) \ - APPLY_UNARY_CAST_OPERATOR(5528, i8toi1) \ - APPLY_UNARY_CAST_OPERATOR(5529, i1tof4) \ - APPLY_UNARY_CAST_OPERATOR(5531, i1tof8) \ - APPLY_UNARY_CAST_OPERATOR(5530, f4toi1) \ - APPLY_UNARY_CAST_OPERATOR(5532, f8toi1) \ - APPLY_UNARY_CAST_OPERATOR(5533, int1_bool) \ - APPLY_UNARY_CAST_OPERATOR(5534, bool_int1) \ - APPLY_UNARY_CAST_OPERATOR(3189, int1_interval) \ - APPLY_UNARY_CAST_OPERATOR(3190, int2_interval) \ - APPLY_UNARY_CAST_OPERATOR(3191, int4_interval) \ - APPLY_UNARY_OPERATOR(6108, int1um) \ - APPLY_UNARY_OPERATOR(6107, int1up) \ - APPLY_BINARY_OPERATOR(6109, int1pl) \ - APPLY_BINARY_OPERATOR(6110, int1mi) \ - APPLY_BINARY_OPERATOR(6111, int1mul) \ - APPLY_BINARY_OPERATOR(6112, int1div) \ - APPLY_UNARY_OPERATOR(6113, int1abs) \ - APPLY_BINARY_OPERATOR(6114, int1mod) \ - APPLY_UNARY_OPERATOR(6117, int1inc) \ - APPLY_BINARY_OPERATOR(467, int8eq) \ - APPLY_BINARY_OPERATOR(474, int84eq) \ - APPLY_BINARY_OPERATOR(852, int48eq) \ - APPLY_BINARY_OPERATOR(1856, int82eq) \ - APPLY_BINARY_OPERATOR(1850, int28eq) \ - APPLY_UNARY_OPERATOR(462, int8um) \ - APPLY_UNARY_OPERATOR(1910, int8up) \ - APPLY_BINARY_OPERATOR(463, int8pl) \ - APPLY_BINARY_OPERATOR(464, int8mi) \ - APPLY_BINARY_OPERATOR(465, int8mul) \ - APPLY_BINARY_OPERATOR(466, int8div) \ - APPLY_UNARY_OPERATOR(1230, int8abs) \ - APPLY_BINARY_OPERATOR(945, int8mod) \ - APPLY_UNARY_OPERATOR(1219, int8inc) \ - APPLY_BINARY_OPERATOR(1274, int84pl) \ - APPLY_BINARY_OPERATOR(1275, int84mi) \ - APPLY_BINARY_OPERATOR(1276, int84mul) \ - APPLY_BINARY_OPERATOR(1277, int84div) \ - APPLY_BINARY_OPERATOR(1278, int48pl) \ - APPLY_BINARY_OPERATOR(1279, int48mi) \ - APPLY_BINARY_OPERATOR(1280, int48mul) \ - APPLY_BINARY_OPERATOR(1281, int48div) \ - APPLY_BINARY_OPERATOR(837, int82pl) \ - APPLY_BINARY_OPERATOR(838, int82mi) \ - APPLY_BINARY_OPERATOR(839, int82mul) \ - APPLY_BINARY_OPERATOR(840, int82div) \ - APPLY_BINARY_OPERATOR(841, int28pl) \ - APPLY_BINARY_OPERATOR(942, int28mi) \ - APPLY_BINARY_OPERATOR(943, int28mul) \ - APPLY_BINARY_OPERATOR(948, int28div) \ - APPLY_BINARY_OPERATOR(1904, int8and) \ - APPLY_BINARY_OPERATOR(1905, int8or) \ - APPLY_BINARY_OPERATOR(1906, int8xor) \ - APPLY_BINARY_OPERATOR(1908, int8shl) \ - APPLY_BINARY_OPERATOR(1909, int8shr) \ - APPLY_UNARY_OPERATOR(1907, int8not) \ - APPLY_UNARY_CAST_OPERATOR(3177, int8_bool) \ - APPLY_UNARY_CAST_OPERATOR(3178, bool_int8) \ - APPLY_UNARY_CAST_OPERATOR(481, int48) \ - APPLY_UNARY_CAST_OPERATOR(480, int84) \ - APPLY_UNARY_CAST_OPERATOR(754, int28) \ - APPLY_UNARY_CAST_OPERATOR(714, int82) \ - APPLY_UNARY_CAST_OPERATOR(482, i8tod) \ - APPLY_UNARY_CAST_OPERATOR(483, dtoi8) \ - APPLY_UNARY_CAST_OPERATOR(652, i8tof) \ - APPLY_UNARY_CAST_OPERATOR(653, ftoi8) \ - APPLY_BINARY_OPERATOR(1086, date_eq) \ - APPLY_BINARY_OPERATOR(1140, date_mi) \ - APPLY_BINARY_OPERATOR(1141, date_pli) \ - APPLY_BINARY_OPERATOR(1142, date_mii) \ - APPLY_BINARY_OPERATOR(2340, date_eq_timestamp) \ - APPLY_BINARY_OPERATOR(2353, date_eq_timestamptz) \ - APPLY_BINARY_OPERATOR(2366, timestamp_eq_date) \ - APPLY_BINARY_OPERATOR(2379, timestamptz_eq_date) \ - APPLY_BINARY_OPERATOR(2071, date_pl_interval) \ - APPLY_BINARY_OPERATOR(2072, date_mi_interval) \ - APPLY_UNARY_CAST_OPERATOR(2024, date_timestamp) \ - APPLY_UNARY_CAST_OPERATOR(2029, timestamp_date) \ - APPLY_UNARY_CAST_OPERATOR(1174, date_timestamptz) \ - APPLY_UNARY_CAST_OPERATOR(1178, timestamptz_date) \ - APPLY_UNARY_CAST_OPERATOR(1179, abstime_date) \ - APPLY_UNARY_OPERATOR(3944, time_transform) \ - APPLY_BINARY_OPERATOR(1968, time_scale) \ - APPLY_BINARY_OPERATOR(1145, time_eq) \ - APPLY_UNARY_CAST_OPERATOR(1316, timestamp_time) \ - APPLY_UNARY_CAST_OPERATOR(2019, timestamptz_time) \ - APPLY_BINARY_OPERATOR(2025, datetime_timestamp) \ - APPLY_UNARY_CAST_OPERATOR(1370, time_interval) \ - APPLY_UNARY_CAST_OPERATOR(1419, interval_time) \ - APPLY_BINARY_OPERATOR(1690, time_mi_time) \ - APPLY_BINARY_OPERATOR(1747, time_pl_interval) \ - APPLY_BINARY_OPERATOR(1969, timetz_scale) \ - APPLY_BINARY_OPERATOR(1352, timetz_eq) \ - APPLY_BINARY_OPERATOR(1749, timetz_pl_interval) \ - APPLY_BINARY_OPERATOR(1750, timetz_mi_interval) \ - APPLY_UNARY_CAST_OPERATOR(2046, timetz_time) \ - APPLY_UNARY_CAST_OPERATOR(2047, time_timetz) \ - APPLY_UNARY_CAST_OPERATOR(1388, timestamptz_timetz) \ - APPLY_BINARY_OPERATOR(1297, datetimetz_timestamptz) \ - APPLY_BINARY_OPERATOR(2037, timetz_zone) \ - APPLY_BINARY_OPERATOR(2038, timetz_izone) \ - APPLY_BINARY_OPERATOR(5580, smalldatetime_eq) \ - APPLY_UNARY_OPERATOR(3917, timestamp_transform) \ - APPLY_BINARY_OPERATOR(1961, timestamp_scale) \ - APPLY_BINARY_OPERATOR(1967, timestamptz_scale) \ - APPLY_BINARY_OPERATOR(2052, timestamp_eq) \ - APPLY_BINARY_OPERATOR(2522, timestamp_eq_timestamptz) \ - APPLY_BINARY_OPERATOR(2529, timestamptz_eq_timestamp) \ - APPLY_UNARY_OPERATOR(3918, interval_transform) \ - APPLY_BINARY_OPERATOR(1200, interval_scale) \ - APPLY_BINARY_OPERATOR(1162, interval_eq) \ - APPLY_BINARY_OPERATOR(2031, timestamp_mi) \ - APPLY_UNARY_OPERATOR(2711, interval_justify_interval) \ - APPLY_UNARY_OPERATOR(1175, interval_justify_hours) \ - APPLY_UNARY_OPERATOR(1295, interval_justify_days) \ - APPLY_BINARY_OPERATOR(2032, timestamp_pl_interval) \ - APPLY_BINARY_OPERATOR(2033, timestamp_mi_interval) \ - APPLY_BINARY_OPERATOR(1189, timestamptz_pl_interval) \ - APPLY_BINARY_OPERATOR(1190, timestamptz_mi_interval) \ - APPLY_UNARY_OPERATOR(1168, interval_um) \ - APPLY_BINARY_OPERATOR(1169, interval_pl) \ - APPLY_BINARY_OPERATOR(1170, interval_mi) \ - APPLY_BINARY_OPERATOR(1618, interval_mul) \ - APPLY_BINARY_OPERATOR(1624, mul_d_interval) \ - APPLY_BINARY_OPERATOR(1326, interval_div) \ - APPLY_UNARY_CAST_OPERATOR(2028, timestamp_timestamptz) \ - APPLY_UNARY_CAST_OPERATOR(2027, timestamptz_timestamp) \ - APPLY_TERNARY_CAST_OPERATOR(668, bpchar) \ - APPLY_UNARY_OPERATOR(3097, varchar_transform) \ - APPLY_TERNARY_CAST_OPERATOR(669, varchar) \ - APPLY_BINARY_OPERATOR(1048, bpchareq) \ - APPLY_BINARY_OPERATOR(67, texteq) \ - APPLY_UNARY_OPERATOR(207, float4abs) \ - APPLY_UNARY_OPERATOR(206, float4um) \ - APPLY_UNARY_OPERATOR(1913, float4up) \ - APPLY_UNARY_OPERATOR(221, float8abs) \ - APPLY_UNARY_OPERATOR(220, float8um) \ - APPLY_UNARY_OPERATOR(1914, float8up) \ - APPLY_BINARY_OPERATOR(204, float4pl) \ - APPLY_BINARY_OPERATOR(205, float4mi) \ - APPLY_BINARY_OPERATOR(202, float4mul) \ - APPLY_BINARY_OPERATOR(203, float4div) \ - APPLY_BINARY_OPERATOR(218, float8pl) \ - APPLY_BINARY_OPERATOR(219, float8mi) \ - APPLY_BINARY_OPERATOR(216, float8mul) \ - APPLY_BINARY_OPERATOR(217, float8div) \ - APPLY_BINARY_OPERATOR(287, float4eq) \ - APPLY_BINARY_OPERATOR(293, float8eq) \ - APPLY_UNARY_CAST_OPERATOR(311, ftod) \ - APPLY_UNARY_CAST_OPERATOR(312, dtof) \ - APPLY_UNARY_CAST_OPERATOR(317, dtoi4) \ - APPLY_UNARY_CAST_OPERATOR(237, dtoi2) \ - APPLY_UNARY_CAST_OPERATOR(316, i4tod) \ - APPLY_UNARY_CAST_OPERATOR(235, i2tod) \ - APPLY_UNARY_CAST_OPERATOR(319, ftoi4) \ - APPLY_UNARY_CAST_OPERATOR(238, ftoi2) \ - APPLY_UNARY_CAST_OPERATOR(318, i4tof) \ - APPLY_UNARY_CAST_OPERATOR(236, i2tof) \ - APPLY_UNARY_OPERATOR(228, dround) \ - APPLY_UNARY_OPERATOR(2320, dceil) \ - APPLY_UNARY_OPERATOR(2309, dfloor) \ - APPLY_UNARY_OPERATOR(2310, dsign) \ - APPLY_UNARY_OPERATOR(229, dtrunc) \ - APPLY_UNARY_OPERATOR(230, dsqrt) \ - APPLY_UNARY_OPERATOR(231, dcbrt) \ - APPLY_UNARY_OPERATOR(232, dpow) \ - APPLY_UNARY_OPERATOR(233, dexp) \ - APPLY_UNARY_OPERATOR(234, dlog1) \ - APPLY_UNARY_OPERATOR(1339, dlog10) \ - APPLY_UNARY_OPERATOR(1601, dacos) \ - APPLY_UNARY_OPERATOR(1600, dasin) \ - APPLY_UNARY_OPERATOR(1602, datan) \ - APPLY_UNARY_OPERATOR(1603, datan2) \ - APPLY_UNARY_OPERATOR(1605, dcos) \ - APPLY_UNARY_OPERATOR(1607, dcot) \ - APPLY_UNARY_OPERATOR(1604, dsin) \ - APPLY_UNARY_OPERATOR(1606, dtan) \ - APPLY_UNARY_OPERATOR(1608, degrees) \ - APPLY_UNARY_OPERATOR(1610, dpi) \ - APPLY_UNARY_OPERATOR(1609, radians) \ - APPLY_UNARY_OPERATOR(1598, drandom) \ - APPLY_UNARY_OPERATOR(1599, setseed) \ - APPLY_BINARY_OPERATOR(281, float48pl) \ - APPLY_BINARY_OPERATOR(282, float48mi) \ - APPLY_BINARY_OPERATOR(279, float48mul) \ - APPLY_BINARY_OPERATOR(280, float48div) \ - APPLY_BINARY_OPERATOR(285, float84pl) \ - APPLY_BINARY_OPERATOR(286, float84mi) \ - APPLY_BINARY_OPERATOR(283, float84mul) \ - APPLY_BINARY_OPERATOR(284, float84div) \ - APPLY_BINARY_OPERATOR(299, float48eq) \ - APPLY_BINARY_OPERATOR(305, float84eq) \ - APPLY_UNARY_OPERATOR(3157, numeric_transform) \ - APPLY_BINARY_CAST_OPERATOR(1703, numeric) \ - APPLY_UNARY_OPERATOR(1704, numeric_abs) \ - APPLY_UNARY_OPERATOR(1771, numeric_uminus) \ - APPLY_UNARY_OPERATOR(1915, numeric_uplus) \ - APPLY_UNARY_OPERATOR(1706, numeric_sign) \ - APPLY_UNARY_OPERATOR(1707, numeric_round) \ - APPLY_UNARY_OPERATOR(1709, numeric_trunc) \ - APPLY_UNARY_OPERATOR(2167, numeric_ceil) \ - APPLY_UNARY_OPERATOR(1712, numeric_floor) \ - APPLY_BINARY_OPERATOR(1718, numeric_eq) \ - APPLY_BINARY_OPERATOR(1724, numeric_add) \ - APPLY_BINARY_OPERATOR(1725, numeric_sub) \ - APPLY_BINARY_OPERATOR(1726, numeric_mul) \ - APPLY_BINARY_OPERATOR(1727, numeric_div) \ - APPLY_BINARY_OPERATOR(1980, numeric_div_trunc) \ - APPLY_BINARY_OPERATOR(1729, numeric_mod) \ - APPLY_UNARY_OPERATOR(1764, numeric_inc) \ - APPLY_UNARY_OPERATOR(111, numeric_fac) \ - APPLY_UNARY_OPERATOR(1731, numeric_sqrt) \ - APPLY_UNARY_OPERATOR(1733, numeric_exp) \ - APPLY_UNARY_OPERATOR(1735, numeric_ln) \ - APPLY_BINARY_OPERATOR(1737, numeric_log) \ - APPLY_BINARY_OPERATOR(1739, numeric_power) \ - APPLY_UNARY_CAST_OPERATOR(1740, int4_numeric) \ - APPLY_UNARY_CAST_OPERATOR(1744, numeric_int4) \ - APPLY_UNARY_CAST_OPERATOR(1781, int8_numeric) \ - APPLY_UNARY_CAST_OPERATOR(1779, numeric_int8) \ - APPLY_UNARY_CAST_OPERATOR(1783, numeric_int2) \ - APPLY_UNARY_CAST_OPERATOR(5521, int1_numeric) \ - APPLY_UNARY_CAST_OPERATOR(5522, numeric_int1) \ - APPLY_UNARY_CAST_OPERATOR(1743, float8_numeric) \ - APPLY_UNARY_CAST_OPERATOR(1746, numeric_float8) \ - APPLY_UNARY_CAST_OPERATOR(1742, float4_numeric) \ - APPLY_UNARY_CAST_OPERATOR(1745, numeric_float4) \ - APPLY_BINARY_OPERATOR(2746, int8_avg_accum) \ - APPLY_BINARY_OPERATOR(1963, int4_avg_accum) \ - APPLY_BINARY_OPERATOR(1962, int2_avg_accum) \ - APPLY_BINARY_OPERATOR(5548, int1_avg_accum) \ - APPLY_BINARY_OPERATOR(208, float4_accum) \ - APPLY_BINARY_OPERATOR(222, float8_accum) \ - APPLY_BINARY_OPERATOR(2858, numeric_avg_accum) \ - APPLY_BINARY_OPERATOR(2996, int8_sum_to_int8) \ - APPLY_BINARY_OPERATOR(1842, int8_sum) \ - APPLY_BINARY_OPERATOR(1841, int4_sum) \ - APPLY_BINARY_OPERATOR(1840, int2_sum) \ - APPLY_BINARY_OPERATOR(1236, int8larger) \ - APPLY_BINARY_OPERATOR(768, int4larger) \ - APPLY_BINARY_OPERATOR(770, int2larger) \ - APPLY_BINARY_OPERATOR(6115, int1larger) \ - APPLY_BINARY_OPERATOR(209, float4larger) \ - APPLY_BINARY_OPERATOR(223, float8larger) \ - APPLY_BINARY_OPERATOR(1767, numeric_larger) \ - APPLY_BINARY_OPERATOR(2036, timestamp_larger) \ - APPLY_BINARY_OPERATOR(1138, date_larger) \ - APPLY_BINARY_OPERATOR(1063, bpchar_larger) \ - APPLY_BINARY_OPERATOR(458, text_larger) \ - APPLY_BINARY_OPERATOR(1237, int8smaller) \ - APPLY_BINARY_OPERATOR(769, int4smaller) \ - APPLY_BINARY_OPERATOR(771, int2smaller) \ - APPLY_BINARY_OPERATOR(211, float4smaller) \ - APPLY_BINARY_OPERATOR(224, float8smaller) \ - APPLY_BINARY_OPERATOR(1766, numeric_smaller) \ - APPLY_BINARY_OPERATOR(2035, timestamp_smaller) \ - APPLY_BINARY_OPERATOR(1139, date_smaller) \ - APPLY_BINARY_OPERATOR(1064, bpchar_smaller) \ - APPLY_BINARY_OPERATOR(459, text_smaller) \ - APPLY_BINARY_OPERATOR(468, int8ne) \ - APPLY_BINARY_OPERATOR(469, int8lt) \ - APPLY_BINARY_OPERATOR(470, int8gt) \ - APPLY_BINARY_OPERATOR(471, int8le) \ - APPLY_BINARY_OPERATOR(472, int8ge) \ - APPLY_BINARY_OPERATOR(144, int4ne) \ - APPLY_BINARY_OPERATOR(66, int4lt) \ - APPLY_BINARY_OPERATOR(147, int4gt) \ - APPLY_BINARY_OPERATOR(149, int4le) \ - APPLY_BINARY_OPERATOR(150, int4ge) \ - APPLY_BINARY_OPERATOR(145, int2ne) \ - APPLY_BINARY_OPERATOR(64, int2lt) \ - APPLY_BINARY_OPERATOR(146, int2gt) \ - APPLY_BINARY_OPERATOR(148, int2le) \ - APPLY_BINARY_OPERATOR(151, int2ge) \ - APPLY_BINARY_OPERATOR(5508, int1ne) \ - APPLY_BINARY_OPERATOR(5509, int1lt) \ - APPLY_BINARY_OPERATOR(5511, int1gt) \ - APPLY_BINARY_OPERATOR(5510, int1le) \ - APPLY_BINARY_OPERATOR(5512, int1ge) \ - APPLY_BINARY_OPERATOR(288, float4ne) \ - APPLY_BINARY_OPERATOR(289, float4lt) \ - APPLY_BINARY_OPERATOR(291, float4gt) \ - APPLY_BINARY_OPERATOR(290, float4le) \ - APPLY_BINARY_OPERATOR(292, float4ge) \ - APPLY_BINARY_OPERATOR(294, float8ne) \ - APPLY_BINARY_OPERATOR(295, float8lt) \ - APPLY_BINARY_OPERATOR(297, float8gt) \ - APPLY_BINARY_OPERATOR(296, float8le) \ - APPLY_BINARY_OPERATOR(298, float8ge) \ - APPLY_BINARY_OPERATOR(1719, numeric_ne) \ - APPLY_BINARY_OPERATOR(1722, numeric_lt) \ - APPLY_BINARY_OPERATOR(1720, numeric_gt) \ - APPLY_BINARY_OPERATOR(1723, numeric_le) \ - APPLY_BINARY_OPERATOR(1721, numeric_ge) \ - APPLY_BINARY_OPERATOR(2053, timestamp_ne) \ - APPLY_BINARY_OPERATOR(2054, timestamp_lt) \ - APPLY_BINARY_OPERATOR(2057, timestamp_gt) \ - APPLY_BINARY_OPERATOR(2055, timestamp_le) \ - APPLY_BINARY_OPERATOR(2056, timestamp_ge) \ - APPLY_BINARY_OPERATOR(1091, date_ne) \ - APPLY_BINARY_OPERATOR(1087, date_lt) \ - APPLY_BINARY_OPERATOR(1089, date_gt) \ - APPLY_BINARY_OPERATOR(1088, date_le) \ - APPLY_BINARY_OPERATOR(1090, date_ge) \ - APPLY_BINARY_OPERATOR(1053, bpcharne) \ - APPLY_BINARY_OPERATOR(1049, bpcharlt) \ - APPLY_BINARY_OPERATOR(1051, bpchargt) \ - APPLY_BINARY_OPERATOR(1050, bpcharle) \ - APPLY_BINARY_OPERATOR(1052, bpcharge) \ - APPLY_BINARY_OPERATOR(157, textne) \ - APPLY_BINARY_OPERATOR(740, text_lt) \ - APPLY_BINARY_OPERATOR(742, text_gt) \ - APPLY_BINARY_OPERATOR(741, text_le) \ - APPLY_BINARY_OPERATOR(743, text_ge) - -// aggregate operators have different identifiers for the same functions, so we define them separately -// nevertheless, the operators are defined as well in APPLY_OPERATORS() -#define APPLY_AGG_OPERATORS() \ - APPLY_BINARY_OPERATOR(INT8AVGFUNCOID, int8_avg_accum) \ - APPLY_BINARY_OPERATOR(INT4AVGFUNCOID, int4_avg_accum) \ - APPLY_BINARY_OPERATOR(INT2AVGFUNCOID, int2_avg_accum) \ - APPLY_BINARY_OPERATOR(5537, int1_avg_accum) \ - APPLY_BINARY_OPERATOR(2104, float4_accum) \ - APPLY_BINARY_OPERATOR(2105, float8_accum) \ - APPLY_BINARY_OPERATOR(NUMERICAVGFUNCOID, numeric_avg_accum) \ - APPLY_BINARY_OPERATOR(2996, int8_sum_to_int8) \ - APPLY_BINARY_OPERATOR(INT8SUMFUNCOID, int8_sum) \ - APPLY_BINARY_OPERATOR(INT4SUMFUNCOID, int4_sum) \ - APPLY_BINARY_OPERATOR(INT2SUMFUNCOID, int2_sum) \ - APPLY_BINARY_OPERATOR(2110, float4pl) \ - APPLY_BINARY_OPERATOR(2111, float8pl) \ - APPLY_BINARY_OPERATOR(NUMERICSUMFUNCOID, numeric_add) \ - APPLY_BINARY_OPERATOR(INT8LARGERFUNCOID, int8larger) \ - APPLY_BINARY_OPERATOR(INT4LARGERFUNCOID, int4larger) \ - APPLY_BINARY_OPERATOR(INT2LARGERFUNCOID, int2larger) \ - APPLY_BINARY_OPERATOR(5538, int1larger) \ - APPLY_BINARY_OPERATOR(2119, float4larger) \ - APPLY_BINARY_OPERATOR(2120, float8larger) \ - APPLY_BINARY_OPERATOR(NUMERICLARGERFUNCOID, numeric_larger) \ - APPLY_BINARY_OPERATOR(2126, timestamp_larger) \ - APPLY_BINARY_OPERATOR(2122, date_larger) \ - APPLY_BINARY_OPERATOR(2244, bpchar_larger) \ - APPLY_BINARY_OPERATOR(2129, text_larger) \ - APPLY_BINARY_OPERATOR(INT8SMALLERFUNCOID, int8smaller) \ - APPLY_BINARY_OPERATOR(INT4SMALLERFUNCOID, int4smaller) \ - APPLY_BINARY_OPERATOR(INT2SMALLERFUNCOID, int2smaller) \ - APPLY_BINARY_OPERATOR(2135, float4smaller) \ - APPLY_BINARY_OPERATOR(2120, float8smaller) \ - APPLY_BINARY_OPERATOR(NUMERICSMALLERFUNCOID, numeric_smaller) \ - APPLY_BINARY_OPERATOR(2142, timestamp_smaller) \ - APPLY_BINARY_OPERATOR(2138, date_smaller) \ - APPLY_BINARY_OPERATOR(2245, bpchar_smaller) \ - APPLY_BINARY_OPERATOR(2145, text_smaller) \ - APPLY_UNARY_OPERATOR(2803, int8inc) - -#endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan.cpp b/src/gausskernel/storage/mot/jit_exec/jit_plan.cpp index 594c37ec1..7c17308e7 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_plan.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan.cpp @@ -28,11 +28,14 @@ */ #include "global.h" #include "jit_plan.h" +#include "jit_plan_sp.h" #include "jit_common.h" #include "mm_session_api.h" #include "utilities.h" #include "nodes/pg_list.h" #include "catalog/pg_aggregate.h" +#include "cycles.h" +#include "mot_internal.h" #include @@ -91,7 +94,7 @@ static bool countWhereClauseEqualsLeaves(Query* query, MOT::Table* table, MOT::I return false; \ } -static bool CheckQueryAttributes(const Query* query, bool allowSorting, bool allowAggregate, bool allowSublink) +bool CheckQueryAttributes(const Query* query, bool allowSorting, bool allowAggregate, bool allowSublink) { checkJittableAttribute(query, hasWindowFuncs); checkJittableAttribute(query, hasDistinctOn); @@ -179,12 +182,13 @@ static void freeSelectExprArray(JitSelectExprArray* expr_array) { if (expr_array->_exprs != nullptr) { for (int i = 0; i < expr_array->_count; ++i) { - if (expr_array->_exprs[i]._column_expr) { - freeExpr((JitExpr*)expr_array->_exprs[i]._column_expr); + if (expr_array->_exprs[i]._expr) { + freeExpr((JitExpr*)expr_array->_exprs[i]._expr); } } MOT::MemSessionFree(expr_array->_exprs); expr_array->_exprs = nullptr; + expr_array->_count = 0; } } @@ -246,7 +250,7 @@ static bool prepareTargetExpressions(Query* query, JitColumnExprArray* expr_arra static bool prepareSelectExpressions(Query* query, JitSelectExprArray* expr_array) { - MOT_LOG_TRACE("Preparing target expressions"); + MOT_LOG_TRACE("Preparing select expressions"); bool result = false; int expr_count = getNonJunkTargetEntryCount(query); MOT_LOG_TRACE("Counted %d non-junk target expressions", expr_count); @@ -255,9 +259,8 @@ static bool prepareSelectExpressions(Query* query, JitSelectExprArray* expr_arra } else { // retrieve search expressions if (!getSelectExpressions(query, expr_array)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Prepare JIT plan", "Failed to collect target expressions"); - MOT::MemSessionFree(expr_array->_exprs); - expr_array->_exprs = nullptr; + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Prepare JIT plan", "Failed to collect select expressions"); + freeSelectExprArray(expr_array); } else { result = true; } @@ -291,22 +294,38 @@ static bool prepareRangeSearchExpressions( // Count all leaves in parsed WHERE clause tree, that belong to the given table // but do not refer to the index columns -static bool countFilters(Query* query, MOT::Table* table, MOT::Index* index, int* count, JitColumnExprArray* pkey_exprs) +static bool countFilters(Query* query, MOT::Table* table, MOT::Index* index, int* count, int* oneTimeFilterCount, + JitColumnExprArray* pkey_exprs, JoinClauseType join_clause_type) { MOT_LOG_TRACE("Counting filters for table %s, index %s", table->GetTableName().c_str(), index->GetName().c_str()); - FilterCounter filter_counter(count); + FilterCounter filter_counter(count, oneTimeFilterCount); Node* quals = query->jointree->quals; bool result = true; if (quals == nullptr) { *count = 0; } else { - result = - visitSearchExpressions(query, table, index, (Expr*)&quals[0], false, &filter_counter, false, pkey_exprs); + bool includeJoinExprs = (join_clause_type == JoinClauseImplicit); + result = visitSearchExpressions( + query, table, index, (Expr*)&quals[0], false, &filter_counter, includeJoinExprs, pkey_exprs); + if (result && (join_clause_type == JoinClauseExplicit)) { + // add filters from explicit join clause + quals = getJoinQualifiers(query); + if (quals == nullptr) { + MOT_LOG_TRACE("Query is not jittable: JOIN clause has unexpectedly no qualifiers"); + } else { + MOT_LOG_TRACE("Adding JOIN clause qualifiers for filter count"); + if (!visitSearchExpressions( + query, table, index, (Expr*)&quals[0], false, &filter_counter, true, pkey_exprs)) { + MOT_LOG_TRACE("Failed to collect range search expressions from JOIN qualifiers"); + result = false; + } + } + } } if (!result) { MOT_LOG_TRACE("Failed to count number of filters"); } else { - MOT_LOG_TRACE("Found %d filters", *count); + MOT_LOG_TRACE("Found %d filters (%d one-time filters)", *count, *oneTimeFilterCount); } return result; } @@ -314,8 +333,8 @@ static bool countFilters(Query* query, MOT::Table* table, MOT::Index* index, int static bool allocFilterArray(JitFilterArray* filter_array, int filter_count) { bool result = false; - size_t alloc_size = filter_count * sizeof(JitFilter); - filter_array->_scan_filters = (JitFilter*)MOT::MemSessionAlloc(alloc_size); + size_t alloc_size = filter_count * sizeof(JitExpr*); + filter_array->_scan_filters = (JitExpr**)MOT::MemSessionAlloc(alloc_size); if (filter_array->_scan_filters == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Prepare JIT plan", @@ -335,8 +354,9 @@ static void freeFilterArray(JitFilterArray* filter_array) { if (filter_array->_scan_filters != nullptr) { for (int i = 0; i < filter_array->_filter_count; ++i) { - freeExpr(filter_array->_scan_filters[i]._lhs_operand); - freeExpr(filter_array->_scan_filters[i]._rhs_operand); + if (filter_array->_scan_filters[i] != nullptr) { + freeExpr(filter_array->_scan_filters[i]); + } } MOT::MemSessionFree(filter_array->_scan_filters); filter_array->_scan_filters = nullptr; @@ -344,17 +364,34 @@ static void freeFilterArray(JitFilterArray* filter_array) } static bool getFilters(Query* query, MOT::Table* table, MOT::Index* index, JitFilterArray* filter_array, int* count, - JitColumnExprArray* pkey_exprs) + JitFilterArray* oneTimeFilterArray, int* oneTimeFilterCount, JitColumnExprArray* pkey_exprs, + JoinClauseType join_clause_type) { MOT_LOG_TRACE("Retrieving filters for table %s, index %s", table->GetTableName().c_str(), index->GetName().c_str()); - FilterCollector filter_collector(query, filter_array, count); + FilterCollector filter_collector(query, filter_array, count, oneTimeFilterArray, oneTimeFilterCount); Node* quals = query->jointree->quals; bool result = true; if (quals == nullptr) { *count = 0; + *oneTimeFilterCount = 0; } else { - result = - visitSearchExpressions(query, table, index, (Expr*)&quals[0], false, &filter_collector, false, pkey_exprs); + bool includeJoinExprs = (join_clause_type == JoinClauseImplicit); + result = visitSearchExpressions( + query, table, index, (Expr*)&quals[0], false, &filter_collector, includeJoinExprs, pkey_exprs); + if (result && (join_clause_type == JoinClauseExplicit)) { + // add filters from explicit join clause + quals = getJoinQualifiers(query); + if (quals == nullptr) { + MOT_LOG_TRACE("Query is not jittable: JOIN clause has unexpectedly no qualifiers"); + } else { + MOT_LOG_TRACE("Adding JOIN clause qualifiers for filter collector"); + if (!visitSearchExpressions( + query, table, index, (Expr*)&quals[0], false, &filter_collector, true, pkey_exprs)) { + MOT_LOG_TRACE("Failed to collect range search expressions from JOIN qualifiers"); + result = false; + } + } + } } if (!result) { MOT_LOG_TRACE("Failed to retrieve filters"); @@ -364,37 +401,67 @@ static bool getFilters(Query* query, MOT::Table* table, MOT::Index* index, JitFi return result; } -static bool prepareFilters( - Query* query, MOT::Table* table, MOT::Index* index, JitFilterArray* filter_array, JitColumnExprArray* pkey_exprs) +static bool prepareFilters(Query* query, MOT::Table* table, MOT::Index* index, JitFilterArray* filter_array, + JitFilterArray* oneTimeFilterArray, JitColumnExprArray* pkey_exprs, JoinClauseType join_clause_type) { MOT_LOG_TRACE( "Preparing search filters on table %s, index %s", table->GetTableName().c_str(), index->GetName().c_str()); - bool result = false; // retrieve filters int filter_count = 0; - if (!countFilters(query, table, index, &filter_count, pkey_exprs)) { + int oneTimeFilterCount = 0; + if (!countFilters(query, table, index, &filter_count, &oneTimeFilterCount, pkey_exprs, join_clause_type)) { MOT_LOG_TRACE("Failed to count filter expressions"); - } else if (filter_count == 0) { - result = true; - } else { - // allocate filter array - result = allocFilterArray(filter_array, filter_count); - if (!result) { + return false; + } + if ((filter_count == 0) && (oneTimeFilterCount == 0)) { + return true; + } + + // allocate filter array + if (filter_count > 0) { + if (!allocFilterArray(filter_array, filter_count)) { MOT_LOG_TRACE("Failed to allocate filter array with %d items", filter_count); - } else { - int collected_count = 0; - result = getFilters(query, table, index, filter_array, &collected_count, pkey_exprs); - if (!result) { - MOT_LOG_TRACE("Failed to collect filters"); - freeFilterArray(filter_array); - } else { - MOT_LOG_TRACE("Collected %d filters", collected_count); - } + return false; } } - return result; + // allocate one-time filter array + if (oneTimeFilterCount > 0) { + MOT_LOG_TRACE("Counted %d one-time filters", oneTimeFilterCount); + if (oneTimeFilterArray == nullptr) { + MOT_LOG_TRACE("Missing one-time filter array, while there are %d one-time filters", oneTimeFilterCount); + return false; + } + if (!allocFilterArray(oneTimeFilterArray, oneTimeFilterCount)) { + MOT_LOG_TRACE("Failed to allocate one-time filter array with %d items", oneTimeFilterCount); + return false; + } + } + + int collected_count = 0; + int oneTimeCollectedCount = 0; + if (!getFilters(query, + table, + index, + filter_array, + &collected_count, + oneTimeFilterArray, + &oneTimeCollectedCount, + pkey_exprs, + join_clause_type)) { + MOT_LOG_TRACE("Failed to collect filters"); + if (filter_count > 0) { + freeFilterArray(filter_array); + } + if (oneTimeFilterCount > 0) { + freeFilterArray(oneTimeFilterArray); + } + return false; + } + + MOT_LOG_TRACE("Collected %d filters (%d one-time filters)", collected_count, oneTimeCollectedCount); + return true; } static TargetEntry* getRefTargetEntry(const List* targetList, int ref_index) @@ -418,6 +485,7 @@ static bool getSortClauseColumns( *column_count = 0; ListCell* lc = nullptr; + JitQuerySortOrder commonSortOrder = JIT_QUERY_SORT_INVALID; foreach (lc, query->sortClause) { SortGroupClause* sgc = (SortGroupClause*)lfirst(lc); if (sgc->groupSet) { @@ -425,6 +493,18 @@ static bool getSortClauseColumns( return false; } + JitQuerySortOrder sortOrder = ClassifyOperatorSortOrder(sgc->sortop); + if (sortOrder == JIT_QUERY_SORT_INVALID) { + MOT_LOG_TRACE("getSortClauseColumns(): Failed to classify sort clause operator"); + return false; + } + if (commonSortOrder == JIT_QUERY_SORT_INVALID) { + commonSortOrder = sortOrder; + } else if (commonSortOrder != sortOrder) { + MOT_LOG_TRACE("getSortClauseColumns(): Sort clause specifying varying sort direction"); + return false; + } + int te_index = sgc->tleSortGroupRef; TargetEntry* te = getRefTargetEntry(query->targetList, te_index); if (te == nullptr) { @@ -439,7 +519,7 @@ static bool getSortClauseColumns( int table_column_id = ((Var*)te->expr)->varattno; int index_column_id = MapTableColumnToIndex(table, index, table_column_id); - if (index_column_id >= key_column_count) { + if (index_column_id < 0) { MOT_LOG_TRACE("getSortClauseColumns(): Disqualifying query - ORDER BY clause references invalid index " "column %d from table column %d", index_column_id, @@ -455,13 +535,107 @@ static bool getSortClauseColumns( return true; } -static bool isJittableSortClause(Query* query, MOT::Table* table, MOT::Index* index, int prefix_column_count) +// Based on functions make_sort_from_sortclauses and getSortClauseColumns +static bool isJittableNonNativeSortClause(Query* query, JitRangeSelectPlan* jitRangeSelectPlan, MOT::Index* index) { - // the ordered-by columns is an ordered list that must exhibit the following properties: - // 1. columns must appear in order of index column - // 2. it must contain a full prefix of the missing columns in the WHERE clause full prefix - // This means that the ORDERED BY clause may contain some redundant column already found in the WHERE clause, - // even with "holes", but they must be in the same order of the index columns. + ListCell* l = nullptr; + int numSortKeys = 0; + + MOT_ASSERT(jitRangeSelectPlan->m_nonNativeSortParams == nullptr); + + if (jitRangeSelectPlan->m_nonNativeSortParams) { + MOT_LOG_TRACE("isJittableNonNativeSortClause(): non native sort plan already exists %p", + jitRangeSelectPlan->m_nonNativeSortParams); + MOT_LOG_ERROR("Non native sort plan already exists"); + + return false; + } + + int numCols = list_length(query->sortClause); + + JitNonNativeSortParams* jitNonNativeSortParams = AllocJitNonNativeSortParams(numCols, JIT_CONTEXT_LOCAL); + + if (!jitNonNativeSortParams) { + MOT_LOG_ERROR("Failed to create JitNonNativeSortParams with %d columns).", numCols); + + return false; + } + + do { + foreach (l, query->sortClause) { + SortGroupClause* sortCl = (SortGroupClause*)lfirst(l); + if (sortCl->groupSet) { + MOT_LOG_TRACE("isJittableNonNativeSortClause(): Found groupSet flag in sort group clause"); + break; + } + + int teIndex = sortCl->tleSortGroupRef; + TargetEntry* te = getRefTargetEntry(query->targetList, teIndex); + if (te == nullptr) { + MOT_LOG_TRACE("isJittableNonNativeSortClause(): Cannot find TargetEntry by ref-index %d", teIndex); + break; + } + + if (te->expr->type != T_Var) { + MOT_LOG_TRACE("isJittableNonNativeSortClause(): TargetEntry sub-expression is not Var expression"); + break; + } + + if (te->resjunk) { + MOT_LOG_TRACE( + "isJittableNonNativeSortClause(): TargetEntry is marked as resjunk and cannot be used for sorting"); + break; + } + + // collations supports only T_Var for now. Might work with other types. + jitNonNativeSortParams->sortColIdx[numSortKeys] = te->resno; + jitNonNativeSortParams->sortOperators[numSortKeys] = sortCl->sortop; + jitNonNativeSortParams->collations[numSortKeys] = + ((const Var*)te->expr)->varcollid; // exprCollation((Node*)te->expr); + jitNonNativeSortParams->nullsFirst[numSortKeys] = sortCl->nulls_first; + numSortKeys++; + } + + if (numSortKeys != jitNonNativeSortParams->numCols) { + MOT_LOG_TRACE("isJittableNonNativeSortClause(): Number of sortClause is different from scanned entries. " + "scanned entries: %d, number of sortClause: %d", + numSortKeys, + jitNonNativeSortParams->numCols); + break; + } + + // update the rest of variables in JitNonNativeSortParams before update the main plan. + jitNonNativeSortParams->plan_node_id = 0; + jitNonNativeSortParams->bound = jitRangeSelectPlan->_limit_count; + + // iterating the results always forward, as the tuple sort is already sorted using the order direction. + jitNonNativeSortParams->scanDir = ScanDirection::ForwardScanDirection; + + // set jitNonNativeSortParams in main plan. + jitRangeSelectPlan->m_nonNativeSortParams = jitNonNativeSortParams; + + // non-native sort will handle the limit count instead of the jit function. + jitRangeSelectPlan->_limit_count = 0; + + return true; + } while (false); + + // bad flow + DestroyJitNonNativeSortParams(jitNonNativeSortParams, JIT_CONTEXT_LOCAL); + jitNonNativeSortParams = nullptr; + + return false; +} + +static bool isJittableSortClause(Query* query, JitIndexScan* indexScan, MOT::Index* index) +{ + // The ordered-by columns is an ordered list that must exhibit the following properties: + // 1. Columns in sort clause must appear in the same order of index column + // 2. All sort columns must specify the same sort order (either all ASC or all DESC) + // 3. The union of the columns in the index scan that appear with EQUALS and the columns in the sort clause + // combined should together form a full prefix + // This means that the ORDERED BY clause may contain some redundant columns already found in the WHERE clause + MOT::Table* table = indexScan->_table; int key_column_count = index->GetNumFields(); int* column_array = (int*)calloc(key_column_count, sizeof(int)); if (column_array == nullptr) { @@ -475,41 +649,67 @@ static bool isJittableSortClause(Query* query, MOT::Table* table, MOT::Index* in free(column_array); return false; } + if (column_count == 0) { + MOT_LOG_TRACE("isJittableSortClause(): No sort clause found"); + free(column_array); + return true; + } - // now check all columns are in order (according to index order) - for (int i = 1; i < column_count; ++i) { - if (column_array[i] < column_array[i - 1]) { - MOT_LOG_TRACE("isJittableSortClause(): Disqualifying query - ORDER BY clause mismatches index %s order", - index->GetName().c_str()); + // verify all sort clause columns appear as in index order and with same sort direction + // pay attention that sort clause is not jittable if it refers any non-index columns, or if sort clauses specify + // varying sort directions (see getSortClauseColumns()) + int prevSortColumn = -1; + for (int i = 0; i < column_count; ++i) { + int sortColumn = column_array[i]; + if (sortColumn < prevSortColumn) { // we expect ascending index columns with possible holes + MOT_LOG_TRACE("isJittableSortClause(): Disqualifying query - ORDER BY clause field %d referring index %s" + " column %d and table column %d cannot be used by index due to non-full prefix (expecting" + " index column %d referring table column %d)", + i, + index->GetName().c_str(), + sortColumn, + index->GetColumnKeyFields()[sortColumn], + i, + index->GetColumnKeyFields()[i]); free(column_array); return false; } + prevSortColumn = sortColumn; } - // now check that sort clause columns contains a full-prefix of columns not in WHERE clause (i.e. no "holes" in - // excess columns) - int column_id = 0; - while ((column_id < column_count) && (column_array[column_id] < prefix_column_count)) { - ++column_id; + // now unify index scan columns having EQUALS with sort clause columns + set unifiedColumns; + for (int i = 0; i < column_count; ++i) { + (void)unifiedColumns.insert(column_array[i]); + MOT_LOG_TRACE("isJittableSortClause(): Adding sort clause column %d", column_array[i]); } - int last_excess_column = prefix_column_count; - while (column_id < column_count) { - if (column_array[column_id] != last_excess_column) { - MOT_LOG_TRACE("isJittableSortClause(): Disqualifying query - ORDER BY clause does not contain full-prefix " - "of missing columns in WHERE clause (hole found at column %d)", - column_id); + for (int i = 0; i < indexScan->_search_exprs._count; ++i) { + if (indexScan->_search_exprs._exprs[i]._op_class == JIT_WOC_EQUALS) { + int columnId = indexScan->_search_exprs._exprs[i]._index_column_id; + (void)unifiedColumns.insert(columnId); + MOT_LOG_TRACE("isJittableSortClause(): Adding WHERE clause EQUALS column %d", columnId); + } + } + + // now verify we have a full prefix of index columns + int i = 0; + set::iterator itr = unifiedColumns.begin(); + while (itr != unifiedColumns.end()) { + int column = *itr; + if (column != i) { + MOT_LOG_TRACE("isJittableSortClause(): Sort clause is not jittable - found hole"); free(column_array); return false; } - ++last_excess_column; - ++column_id; + ++i; + ++itr; } free(column_array); return true; } -static bool isPlanSortOrderValid(Query* query, JitRangeSelectPlan* plan) +static bool isPlanSortOrderValid(Query* query, JitRangeScanPlan* plan) { bool result = false; @@ -517,257 +717,17 @@ static bool isPlanSortOrderValid(Query* query, JitRangeSelectPlan* plan) // not having a sort clause is fine result = true; } else { - MOT::Index* index = plan->_index_scan._table->GetIndex(plan->_index_scan._index_id); - result = isJittableSortClause(query, plan->_index_scan._table, index, plan->_index_scan._column_count); + MOT::Index* index = plan->_index_scan._index; + result = isJittableSortClause(query, &plan->_index_scan, index); if (!result) { - MOT_LOG_TRACE("isPlanSortOrderValid(): Disqualifying query - Sort clause is not jittable") - } - } - - return result; -} - -static int evalConstExpr(Expr* expr) -{ - int result = -1; - - // we expect to see either a Const or a cast to int8 of a Const - Const* const_expr = nullptr; - if (expr->type == T_Const) { - MOT_LOG_TRACE("evalConstExpr(): Found direct const expression"); - const_expr = (Const*)expr; - } else if (expr->type == T_FuncExpr) { - FuncExpr* func_expr = (FuncExpr*)expr; - if (func_expr->funcid == 481) { // cast to int8 - Expr* sub_expr = (Expr*)linitial(func_expr->args); - if (sub_expr->type == T_Const) { - MOT_LOG_TRACE("evalConstExpr(): Found const expression within cast to int8 function expression"); - const_expr = (Const*)sub_expr; - } - } - } - - if (const_expr != nullptr) { - // extract integer value - result = const_expr->constvalue; - MOT_LOG_TRACE("evalConstExpr(): Expression evaluated to constant value %d", result); - } else { - MOT_LOG_TRACE("evalConstExpr(): Could not infer const expression"); - } - - return result; -} - -static bool getLimitCount(Query* query, int* limit_count) -{ - bool result = false; - if (query->limitOffset) { - MOT_LOG_TRACE("getLimitCount(): invalid limit clause - encountered limitOffset clause"); - } else if (query->limitCount) { - *limit_count = evalConstExpr((Expr*)query->limitCount); - result = true; - } else { - *limit_count = 0; // no limit clause - result = true; - } - return result; -} - -static inline bool isValidAggregateResultType(int restype) -{ - bool result = false; - if ((restype == INT1OID) || (restype == INT2OID) || (restype == INT4OID) || (restype == INT8OID) || - (restype == FLOAT4OID) || (restype == FLOAT8OID) || (restype == NUMERICOID)) { - result = true; - } - return result; -} - -static bool isAvgAggregateOperator(int funcid) -{ - bool result = false; - if ((funcid == INT8AVGFUNCOID) /* int8_avg_accum */ || (funcid == INT4AVGFUNCOID) /* int4_avg_accum */ || - (funcid == INT2AVGFUNCOID) /* int2_avg_accum */ || (funcid == 5537) /* int1_avg_accum */ || - (funcid == 2104) /* float4_accum */ || (funcid == 2105) /* float8_accum */ || - (funcid == NUMERICAVGFUNCOID) /* numeric_avg_accum */) { - result = true; - } - return result; -} - -static bool isSumAggregateOperator(int funcid) -{ - bool result = false; - if ((funcid == INT8SUMFUNCOID) /* int8_sum */ || (funcid == INT4SUMFUNCOID) /* int4_sum */ || - (funcid == INT2SUMFUNCOID) /* int2_sum */ || (funcid == 2110) /* float4pl */ || - (funcid == 2111) /* float8pl */ || (funcid == NUMERICSUMFUNCOID) /* numeric_sum */) { - result = true; - } - return result; -} - -static bool isMaxAggregateOperator(int funcid) -{ - bool result = false; - if ((funcid == INT8LARGERFUNCOID) /* int8larger */ || (funcid == INT4LARGERFUNCOID) /* int4larger */ || - (funcid == INT2LARGERFUNCOID) /* int2larger */ || (funcid == 5538) /* int1larger */ || - (funcid == 2119) /* float4larger */ || (funcid == 2120) /* float8larger */ || - (funcid == NUMERICLARGERFUNCOID) /* numeric_larger */ || (funcid == 2126) /* timestamp_larger */ || - (funcid == 2122) /* date_larger */ || (funcid == 2244) /* bpchar_larger */ || - (funcid == 2129) /* text_larger */) { - result = true; - } - return result; -} - -static bool isMinAggregateOperator(int funcid) -{ - bool result = false; - if ((funcid == INT8SMALLERFUNCOID) /* int8smaller */ || (funcid == INT4SMALLERFUNCOID) /* int4smaller */ || - (funcid == INT2SMALLERFUNCOID) /* int2smaller */ || // not int1 function was found for MIN operator - (funcid == 2135) /* float4smaller */ || (funcid == 2136) /* float8smaller */ || - (funcid == NUMERICSMALLERFUNCOID) /* numeric_smaller */ || (funcid == 2142) /* timestamp_smaller */ || - (funcid == 2138) /* date_smaller */ || (funcid == 2245) /* bpchar_smaller */ || - (funcid == 2145) /* text_smaller */) { - result = true; - } - return result; -} - -static bool isCountAggregateOperator(int funcid) -{ - bool result = false; - if ((funcid == 2147) /* int8inc_any */ || (funcid == 2803) /* int8inc */) { - result = true; - } - return result; -} - -static JitAggregateOperator classifyAggregateOperator(int funcid) -{ - JitAggregateOperator result = JIT_AGGREGATE_NONE; - if (isAvgAggregateOperator(funcid)) { - result = JIT_AGGREGATE_AVG; - } else if (isSumAggregateOperator(funcid)) { - result = JIT_AGGREGATE_SUM; - } else if (isMaxAggregateOperator(funcid)) { - result = JIT_AGGREGATE_MAX; - } else if (isMinAggregateOperator(funcid)) { - result = JIT_AGGREGATE_MIN; - } else if (isCountAggregateOperator(funcid)) { - result = JIT_AGGREGATE_COUNT; - } - return result; -} - -static int classifyAggregateAvgType(int funcid, int* element_count) -{ - int element_type = -1; - - switch (funcid) { - case INT8AVGFUNCOID: - case NUMERICAVGFUNCOID: - // the current_aggregate is a 2 numeric array - element_type = NUMERICOID; - *element_count = 2; - break; - - case INT4AVGFUNCOID: - case INT2AVGFUNCOID: - case 5537: // int1 avg - // the current_aggregate is a 2 int8 array - element_type = INT8OID; - *element_count = 2; - break; - - case 2104: // float4 - case 2105: // float8 - // the current_aggregate is a 3 float8 array - element_type = FLOAT8OID; - *element_count = 3; - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate AVG() operator function type: %d", funcid); - break; - } - - return element_type; -} - -static inline bool isValidAggregateFunction(int funcid) -{ - bool result = false; - // check for aggregate_dummy leading to numeric_add and others (see src/include/catalog/pg_proc.h and - // pg_aggregate.h) - if (classifyAggregateOperator(funcid) != JIT_AGGREGATE_NONE) { - result = true; - } - return result; -} - -static inline bool isValidAggregateDistinctClause(const List* agg_distinct) -{ - bool result = false; - - if (agg_distinct == nullptr) { - result = true; - } else if (list_length(agg_distinct) != 1) { - MOT_LOG_TRACE("Invalid DISTINCIT specifier with more than one sort clause"); - } else { - SortGroupClause* sgc = (SortGroupClause*)linitial(agg_distinct); - if (sgc->groupSet) { - MOT_LOG_TRACE("Invalid DISTINCIT specifier with group-set flag"); - } else { - result = true; - } - } - - return result; -} - -static bool getTargetEntryAggregateOperator(Query* query, TargetEntry* target_entry, JitAggregate* aggregate) -{ - bool result = false; - - Aggref* agg_ref = (Aggref*)target_entry->expr; - if (agg_ref->aggorder) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with ORDER BY specifiers"); - } else if (!isValidAggregateFunction(agg_ref->aggfnoid)) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator %d", agg_ref->aggfnoid); - } else if (!isValidAggregateResultType(agg_ref->aggtype)) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate result type %d", agg_ref->aggtype); - } else if (!isValidAggregateDistinctClause(agg_ref->aggdistinct)) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate distinct clause"); - } else if (list_length(agg_ref->args) != 1) { - MOT_LOG_TRACE( - "getTargetEntryAggregateOperator(): Unsupported aggregate argument list with length unequal to 1"); - } else { - TargetEntry* sub_te = (TargetEntry*)linitial(agg_ref->args); - if (sub_te->expr->type != T_Var) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with non-column argument"); - } else { - Var* var_expr = (Var*)sub_te->expr; - int result_type = var_expr->vartype; - if (!IsTypeSupported(result_type)) { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with column type %d", - result_type); - } else { - MOT_LOG_TRACE("getTargetEntryAggregateOperator(): target entry for aggregate query is jittable"); - aggregate->_aggreaget_op = classifyAggregateOperator(agg_ref->aggfnoid); - aggregate->_element_type = agg_ref->aggtype; - if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - aggregate->_avg_element_type = - classifyAggregateAvgType(agg_ref->aggfnoid, &aggregate->_avg_element_count); - } else { - aggregate->_avg_element_type = -1; + if (plan->_command_type == JIT_COMMAND_SELECT) { + MOT_LOG_TRACE( + "isPlanSortOrderValid(): Jittable sort clause is not supported. trying non-native Sort clause"); + result = isJittableNonNativeSortClause(query, (JitRangeSelectPlan*)plan, index); + if (!result) { + MOT_LOG_TRACE( + "isPlanSortOrderValid(): Disqualifying query - non native Sort clause is not jittable"); } - aggregate->_func_id = agg_ref->aggfnoid; - aggregate->_table = getRealTable(query, var_expr->varno, var_expr->varattno); - aggregate->_table_column_id = - getRealColumnId(query, var_expr->varno, var_expr->varattno, aggregate->_table); - aggregate->_distinct = (agg_ref->aggdistinct != nullptr) ? true : false; - result = true; } } } @@ -775,47 +735,71 @@ static bool getTargetEntryAggregateOperator(Query* query, TargetEntry* target_en return result; } -static bool getAggregateOperator(Query* query, JitAggregate* aggregate) +static bool getAggregateOperators(Query* query, JitAggregate** aggregates, uint32_t* aggCount) { - bool result = false; + // we allow specifying several aggregators as long as there are only aggregators + *aggCount = 0; + *aggregates = nullptr; + bool nonAggFound = false; - // if an aggregate operator is specified, then only one column can exist - // so we check all target entries, and if one of them specifies an aggregate operator, then - // it must be the only target entry in the query + // first pass: count number of aggregators, and in case there are such verify there are only aggregates ListCell* lc = nullptr; - - bool aggregate_found = false; - int entry_count = list_length(query->targetList); - foreach (lc, query->targetList) { TargetEntry* target_entry = (TargetEntry*)lfirst(lc); - if (target_entry->expr->type == T_Aggref) { + if (target_entry->expr->type != T_Aggref) { + nonAggFound = true; + } else { // found an aggregate target entry - aggregate_found = true; - if (entry_count != 1) { - MOT_LOG_TRACE( - "getAggregateOperator(): Disqualifying query - aggregate must specify only 1 target entry"); + if (nonAggFound) { + MOT_LOG_TRACE("getAggregateOperators(): Mixing aggregate and non-aggregate is not supported"); + *aggCount = 0; + return false; } - result = getTargetEntryAggregateOperator(query, target_entry, aggregate); + ++(*aggCount); } } - if (!aggregate_found) { - // it is fine not to have an aggregate clause - result = true; + if (*aggCount > 0) { + uint32_t allocSize = sizeof(JitAggregate) * (*aggCount); + *aggregates = (JitAggregate*)MOT::MemSessionAlloc(allocSize); + if (*aggregates == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Compile Query", + "Failed to allocate %u bytes for %u aggregates", + allocSize, + *aggCount); + *aggregates = nullptr; + *aggCount = 0; + return false; + } + + // second pass: collect aggregates + int aggIndex = 0; + foreach (lc, query->targetList) { + TargetEntry* target_entry = (TargetEntry*)lfirst(lc); + MOT_ASSERT(target_entry->expr->type == T_Aggref); + // found an aggregate target entry + if (!getTargetEntryAggregateOperator(query, target_entry, *aggregates + aggIndex)) { + MOT::MemSessionFree(*aggregates); + *aggregates = nullptr; + *aggCount = 0; + return false; + } + ++aggIndex; + } } - return result; + return true; } -static double evaluatePlan(const JitRangeSelectPlan* plan) +static double evaluatePlan(const JitRangeScanPlan* plan) { // currently the value of a range scan plan is how much it matches the used index - MOT::Index* index = plan->_index_scan._table->GetIndex(plan->_index_scan._index_id); + MOT::Index* index = plan->_index_scan._index; return ((double)plan->_index_scan._column_count) / ((double)index->GetNumFields()); } -static bool isJitPlanBetter(JitRangeSelectPlan* candidate_plan, JitRangeSelectPlan* current_plan) +static bool isJitPlanBetter(JitRangeScanPlan* candidate_plan, JitRangeScanPlan* current_plan) { bool result = false; @@ -845,6 +829,7 @@ static JitPlan* JitPrepareInsertPlan(Query* query, MOT::Table* table) errno_t erc = memset_s(insert_plan, alloc_size, 0, alloc_size); securec_check(erc, "\0", "\0"); insert_plan->_plan_type = JIT_PLAN_INSERT_QUERY; + insert_plan->_command_type = JIT_COMMAND_INSERT; insert_plan->_table = table; // retrieve insert expressions @@ -883,7 +868,13 @@ static JitPointQueryPlan* JitPreparePointQueryPlan( // prepare search expressions and optional filters MOT::Index* index = table->GetPrimaryIndex(); if (!prepareSearchExpressions(query, table, index, &plan->_query._search_exprs) || - !prepareFilters(query, table, index, &plan->_query._filters, &plan->_query._search_exprs)) { + !prepareFilters(query, + table, + index, + &plan->_query._filters, + &plan->_query.m_oneTimeFilters, + &plan->_query._search_exprs, + JoinClauseNone)) { MOT_LOG_TRACE("Failed to prepare search expressions or filters"); // cleanup JitDestroyPlan((JitPlan*)plan); @@ -944,8 +935,8 @@ static JitPlan* JitPrepareSelectPlan(Query* query, MOT::Table* table) JitSelectPlan* select_plan = (JitSelectPlan*)JitPreparePointQueryPlan(query, table, alloc_size, JIT_COMMAND_SELECT); if (select_plan != nullptr) { if (!prepareSelectExpressions(query, &select_plan->_select_exprs)) { - MOT_LOG_TRACE("Failed to prepare target expressions"); - JitDestroyPlan((JitPlan*)plan); + MOT_LOG_TRACE("Failed to prepare select expressions"); + JitDestroyPlan((JitPlan*)select_plan); } else { plan = (JitPlan*)select_plan; } @@ -957,14 +948,44 @@ static JitPlan* JitPrepareSelectPlan(Query* query, MOT::Table* table) return plan; } -static JitRangeScanPlan* JitPrepareRangeScanPlan(Query* query, MOT::Table* table, int index_id, size_t alloc_size, - JitCommandType command_type, JoinClauseType join_clause_type) +inline void DestroyColumnExprArray(JitColumnExprArray* exprArray) { - MOT::Index* index = table->GetIndex(index_id); - MOT_LOG_TRACE("Preparing Range Scan plan for table %s, index %d (%s)", - table->GetTableName().c_str(), - index_id, - index->GetName().c_str()); + if (exprArray->_exprs != nullptr) { + MOT::MemSessionFree(exprArray->_exprs); + exprArray->_exprs = nullptr; + } +} + +inline bool ConcatColumnExprArray(JitColumnExprArray* lhs, JitColumnExprArray* rhs, JitColumnExprArray* res) +{ + res->_count = lhs->_count + rhs->_count; + uint32_t allocSize = sizeof(JitColumnExpr) * res->_count; + res->_exprs = (JitColumnExpr*)MOT::MemSessionAlloc(allocSize); + if (res->_exprs == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT plan", + "Failed to allocate %u bytes for %d combined column expressions", + allocSize, + res->_count); + return false; + } + for (int i = 0; i < lhs->_count; ++i) { + res->_exprs[i] = lhs->_exprs[i]; + } + for (int i = 0; i < rhs->_count; ++i) { + res->_exprs[lhs->_count + i] = rhs->_exprs[i]; + } + for (int i = 0; i < res->_count; ++i) { + MOT_LOG_TRACE("Combined expr %d: %p", i, res->_exprs[i]._expr->_source_expr); + } + return true; +} + +static JitRangeScanPlan* JitPrepareRangeScanPlan(Query* query, MOT::Table* table, MOT::Index* index, size_t alloc_size, + JitCommandType command_type, JoinClauseType join_clause_type, JitColumnExprArray* outerSearchExprs = nullptr) +{ + MOT_LOG_TRACE( + "Preparing Range Scan plan for table %s, index %s", table->GetTableName().c_str(), index->GetName().c_str()); JitRangeScanPlan* plan = (JitRangeScanPlan*)MOT::MemSessionAlloc(alloc_size); if (plan == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, @@ -972,24 +993,45 @@ static JitRangeScanPlan* JitPrepareRangeScanPlan(Query* query, MOT::Table* table "Failed to allocate %u bytes for %s range scan plan", (unsigned)alloc_size, CommandToString(command_type)); - } else { - errno_t erc = memset_s(plan, alloc_size, 0, alloc_size); - securec_check(erc, "\0", "\0"); - plan->_plan_type = JIT_PLAN_RANGE_SCAN; - plan->_command_type = command_type; - plan->_index_scan._table = table; - - // prepare search expressions and optional filters - plan->_index_scan._index_id = index_id; - if (!prepareRangeSearchExpressions(query, table, index, &plan->_index_scan, join_clause_type) || - !prepareFilters(query, table, index, &plan->_index_scan._filters, &plan->_index_scan._search_exprs)) { - MOT_LOG_TRACE("Failed to prepare search or filters expressions"); - // cleanup - JitDestroyPlan((JitPlan*)plan); - plan = nullptr; - } + return nullptr; } + errno_t erc = memset_s(plan, alloc_size, 0, alloc_size); + securec_check(erc, "\0", "\0"); + plan->_plan_type = JIT_PLAN_RANGE_SCAN; + plan->_command_type = command_type; + plan->_index_scan._table = table; + plan->_index_scan._index = index; + + // prepare search expressions and optional filters + if (!prepareRangeSearchExpressions(query, table, index, &plan->_index_scan, join_clause_type)) { + MOT_LOG_TRACE("Failed to prepare search or filters expressions"); + // cleanup + JitDestroyPlan((JitPlan*)plan); + return nullptr; + } + + // prepare filters - use outer scan search expression as well if specified + JitColumnExprArray* pkeyExprs = &plan->_index_scan._search_exprs; + JitColumnExprArray combinedExprs = {nullptr, 0}; + if (outerSearchExprs != nullptr) { + if (!ConcatColumnExprArray(pkeyExprs, outerSearchExprs, &combinedExprs)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Prepare JIT plan", "Failed to prepare combined expression array"); + JitDestroyPlan((JitPlan*)plan); + return nullptr; + } + pkeyExprs = &combinedExprs; + } + JitFilterArray* filters = &plan->_index_scan._filters; + JitFilterArray* oneTimeFilters = &plan->_index_scan.m_oneTimeFilters; + if (!prepareFilters(query, table, index, filters, oneTimeFilters, pkeyExprs, join_clause_type)) { + MOT_LOG_TRACE("Failed to prepare filters expressions"); + // cleanup + JitDestroyPlan((JitPlan*)plan); + plan = nullptr; + } + DestroyColumnExprArray(&combinedExprs); + if (plan == nullptr) { MOT_LOG_TRACE("Failed to prepare Range scan plan"); } @@ -1000,102 +1042,206 @@ static JitPlan* JitPrepareRangeUpdatePlan(Query* query, MOT::Table* table) { MOT_LOG_TRACE("Preparing range UPDATE plan for table %s", table->GetTableName().c_str()); size_t alloc_size = sizeof(JitRangeUpdatePlan); - int index_id = 0; // primary index - JitRangeUpdatePlan* plan = (JitRangeUpdatePlan*)JitPrepareRangeScanPlan( - query, table, index_id, alloc_size, JIT_COMMAND_UPDATE, JoinClauseNone); + + JitRangeUpdatePlan* plan = nullptr; + + MOT_LOG_TRACE("Attempting to prepare plan, checking %" PRIu16 " indices", table->GetNumIndexes()); + for (uint16_t indexId = 0; indexId < table->GetNumIndexes(); ++indexId) { + MOT::Index* index = table->GetIndex(indexId); + MOT_LOG_TRACE("Attempting to prepare plan with index %d", indexId); + JitRangeUpdatePlan* nextPlan = (JitRangeUpdatePlan*)JitPrepareRangeScanPlan( + query, table, index, alloc_size, JIT_COMMAND_UPDATE, JoinClauseNone); + if (nextPlan == nullptr) { + MOT_LOG_TRACE("Failed to prepare Range UPDATE plan with index %u", indexId); + continue; + } + + if (!prepareTargetExpressions(query, &nextPlan->_update_exprs)) { + MOT_LOG_TRACE("Failed to prepare target expressions for current plan"); + // cleanup + JitDestroyPlan((JitPlan*)nextPlan); + continue; + } + nextPlan->_index_scan._sort_order = JIT_QUERY_SORT_ASCENDING; + nextPlan->_index_scan._scan_direction = JIT_INDEX_SCAN_FORWARD; + + MOT_LOG_TRACE("Found a candidate plan with value %0.2f:", evaluatePlan((JitRangeScanPlan*)nextPlan)); + JitExplainPlan(query, (JitPlan*)nextPlan); + if (plan == nullptr) { + MOT_LOG_TRACE("Using initial plan with index %u (%s)", indexId, index->GetName().c_str()); + plan = nextPlan; + } else if (isJitPlanBetter((JitRangeScanPlan*)nextPlan, (JitRangeScanPlan*)plan)) { + MOT_LOG_TRACE("Plan with index %u (%s) is better than previous plan", indexId, index->GetName().c_str()); + JitDestroyPlan((JitPlan*)plan); + plan = nextPlan; + } else { + MOT_LOG_TRACE("Discarding plan with index %u (%s), not better than previous best plan", + indexId, + index->GetName().c_str()); + JitDestroyPlan((JitPlan*)nextPlan); + } + } + if (plan == nullptr) { MOT_LOG_TRACE("Failed to prepare Range UPDATE plan"); - } else { - if (!prepareTargetExpressions(query, &plan->_update_exprs)) { - MOT_LOG_TRACE("Failed to prepare target expressions"); - // cleanup - JitDestroyPlan((JitPlan*)plan); - plan = nullptr; - } else { - plan->_index_scan._sort_order = JIT_QUERY_SORT_ASCENDING; - plan->_index_scan._scan_direction = JIT_INDEX_SCAN_FORWARD; - } } return (JitPlan*)plan; } -static JitPlan* JitPrepareRangeSelectPlan(Query* query, MOT::Table* table, JoinClauseType join_clause_type) +static JitPlan* JitPrepareRangeSelectPlan( + Query* query, MOT::Table* table, JoinClauseType join_clause_type, JitColumnExprArray* outerSearchExprs = nullptr) { MOT_LOG_TRACE("Preparing range SELECT plan for table %s", table->GetTableName().c_str()); JitRangeSelectPlan* plan = nullptr; - bool clean_plan = false; // the limit count and aggregation can be inferred regardless of plan int limit_count = 0; - JitAggregate aggregate = {JIT_AGGREGATE_NONE, 0, 0, nullptr, 0, 0, 0, false}; - if (!getLimitCount(query, &limit_count) || !getAggregateOperator(query, &aggregate)) { + JitAggregate* aggregates = nullptr; + uint32_t aggCount = 0; + if (!getLimitCount(query, &limit_count) || !getAggregateOperators(query, &aggregates, &aggCount)) { MOT_LOG_TRACE( "JitPrepareRangeSelectPlan(): Disqualifying query - unsupported scan limit count or aggregate operation"); return nullptr; } // now we search for the best index/plan - bool has_aggregate = (aggregate._aggreaget_op != JIT_AGGREGATE_NONE); size_t alloc_size = sizeof(JitRangeSelectPlan); - - for (int index_id = 0; index_id < (int)table->GetNumIndexes(); ++index_id) { - MOT_LOG_TRACE("Attempting to prepare plan with index %d", index_id); + MOT_LOG_TRACE("Attempting to prepare plan, checking %" PRIu16 " indices", table->GetNumIndexes()); + for (uint16_t indexId = 0; indexId < table->GetNumIndexes(); ++indexId) { + MOT::Index* index = table->GetIndex(indexId); + MOT_LOG_TRACE("Attempting to prepare plan with index %u", indexId); JitRangeSelectPlan* next_plan = (JitRangeSelectPlan*)JitPrepareRangeScanPlan( - query, table, index_id, alloc_size, JIT_COMMAND_SELECT, join_clause_type); + query, table, index, alloc_size, JIT_COMMAND_SELECT, join_clause_type, outerSearchExprs); if (next_plan == nullptr) { MOT_LOG_TRACE( - "Failed to prepare range select plan with index %d: failed to prepare range scan plan", index_id); - clean_plan = true; - break; + "Failed to prepare range select plan with index %u: failed to prepare range scan plan", indexId); + continue; } - if (!has_aggregate && !prepareSelectExpressions(query, &next_plan->_select_exprs)) { + if ((aggCount == 0) && !prepareSelectExpressions(query, &next_plan->_select_exprs)) { MOT_LOG_TRACE( - "Failed to prepare range select plan with index %d: failed to prepare select expressions", index_id); + "Failed to prepare range select plan with index %u: failed to prepare select expressions", indexId); JitDestroyPlan((JitPlan*)next_plan); - clean_plan = true; - break; + next_plan = nullptr; + continue; } + next_plan->_index_scan._sort_order = GetQuerySortOrder(query); + next_plan->_index_scan._scan_direction = (next_plan->_index_scan._sort_order == JIT_QUERY_SORT_ASCENDING) + ? JIT_INDEX_SCAN_FORWARD + : JIT_INDEX_SCAN_BACKWARDS; + next_plan->_limit_count = limit_count; + next_plan->m_nonNativeSortParams = nullptr; + // verify sort order is valid (if one is specified) - if (!isPlanSortOrderValid(query, next_plan)) { + if (!isPlanSortOrderValid(query, (JitRangeScanPlan*)next_plan)) { MOT_LOG_TRACE("Disqualifying plan - Query sort order is incompatible with index"); JitDestroyPlan((JitPlan*)next_plan); - } else { - next_plan->_index_scan._sort_order = GetQuerySortOrder(query); - next_plan->_index_scan._scan_direction = (next_plan->_index_scan._sort_order == JIT_QUERY_SORT_ASCENDING) - ? JIT_INDEX_SCAN_FORWARD - : JIT_INDEX_SCAN_BACKWARDS; - next_plan->_limit_count = limit_count; - next_plan->_aggregate = aggregate; - MOT_LOG_TRACE("Found a candidate plan with value %0.2f:", evaluatePlan(next_plan)); - JitExplainPlan(query, (JitPlan*)next_plan); - if (plan == nullptr) { - MOT_LOG_TRACE( - "Using initial plan with index %d (%s)", index_id, table->GetIndex(index_id)->GetName().c_str()); - plan = next_plan; - } else if (isJitPlanBetter(next_plan, plan)) { - MOT_LOG_TRACE("Plan with index %d (%s) is better than previous plan", - index_id, - table->GetIndex(index_id)->GetName().c_str()); - JitDestroyPlan((JitPlan*)plan); - plan = next_plan; - } else { - JitDestroyPlan((JitPlan*)next_plan); - } + next_plan = nullptr; + continue; } - } - if (clean_plan && (plan != nullptr)) { - JitDestroyPlan((JitPlan*)plan); - plan = nullptr; + MOT_LOG_TRACE("Found a candidate plan with value %0.2f:", evaluatePlan((JitRangeScanPlan*)next_plan)); + JitExplainPlan(query, (JitPlan*)next_plan); + if (plan == nullptr) { + MOT_LOG_TRACE("Using initial plan with index %u (%s)", indexId, index->GetName().c_str()); + plan = next_plan; + } else if (isJitPlanBetter((JitRangeScanPlan*)next_plan, (JitRangeScanPlan*)plan)) { + MOT_LOG_TRACE("Plan with index %u (%s) is better than previous plan", indexId, index->GetName().c_str()); + JitDestroyPlan((JitPlan*)plan); + plan = next_plan; + next_plan = nullptr; + } else { + MOT_LOG_TRACE("Discarding plan with index %u (%s), not better than previous best plan", + indexId, + index->GetName().c_str()); + JitDestroyPlan((JitPlan*)next_plan); + next_plan = nullptr; + } } if (plan == nullptr) { MOT_LOG_TRACE("Failed to prepare Range SELECT plan"); + if (aggregates != nullptr) { + MOT::MemSessionFree(aggregates); + aggregates = nullptr; + } + } else { + // we set aggregates only now to avoid double free if pointed by more than one plan + plan->m_aggCount = aggCount; + plan->m_aggregates = aggregates; } + + return (JitPlan*)plan; +} + +static JitPlan* JitPrepareRangeDeletePlan(Query* query, MOT::Table* table) +{ + MOT_LOG_TRACE("Preparing range DELETE plan for table %s", table->GetTableName().c_str()); + + // get the limit count + int limitCount = 0; + if (!getLimitCount(query, &limitCount)) { + MOT_LOG_TRACE("JitPrepareRangeDeletePlan(): Disqualifying query - unsupported scan limit count"); + return nullptr; + } + + JitRangeDeletePlan* plan = nullptr; + + // now we search for the best index/plan + size_t allocSize = sizeof(JitRangeDeletePlan); + MOT_LOG_TRACE("Attempting to prepare plan, checking %" PRIu16 " indices", table->GetNumIndexes()); + for (uint16_t indexId = 0; indexId < table->GetNumIndexes(); ++indexId) { + MOT::Index* index = table->GetIndex(indexId); + MOT_LOG_TRACE("Attempting to prepare plan with index %u", indexId); + JitRangeDeletePlan* nextPlan = (JitRangeDeletePlan*)JitPrepareRangeScanPlan( + query, table, index, allocSize, JIT_COMMAND_DELETE, JoinClauseNone); + + if (nextPlan == nullptr) { + MOT_LOG_TRACE( + "Failed to prepare range delete plan with index %u: failed to prepare range scan plan", indexId); + continue; + } + + nextPlan->_index_scan._sort_order = GetQuerySortOrder(query); + nextPlan->_index_scan._scan_direction = (nextPlan->_index_scan._sort_order == JIT_QUERY_SORT_ASCENDING) + ? JIT_INDEX_SCAN_FORWARD + : JIT_INDEX_SCAN_BACKWARDS; + nextPlan->_limit_count = limitCount; + + // verify sort order is valid (if one is specified) + if (!isPlanSortOrderValid(query, (JitRangeScanPlan*)nextPlan)) { + MOT_LOG_TRACE("Disqualifying plan - Query sort order is incompatible with index"); + JitDestroyPlan((JitPlan*)nextPlan); + nextPlan = nullptr; + continue; + } + + MOT_LOG_TRACE("Found a candidate plan with value %0.2f:", evaluatePlan((JitRangeScanPlan*)nextPlan)); + JitExplainPlan(query, (JitPlan*)nextPlan); + if (plan == nullptr) { + MOT_LOG_TRACE("Using initial plan with index %u (%s)", indexId, index->GetName().c_str()); + plan = nextPlan; + } else if (isJitPlanBetter((JitRangeScanPlan*)nextPlan, (JitRangeScanPlan*)plan)) { + MOT_LOG_TRACE("Plan with index %u (%s) is better than previous plan", indexId, index->GetName().c_str()); + JitDestroyPlan((JitPlan*)plan); + plan = nextPlan; + nextPlan = nullptr; + } else { + MOT_LOG_TRACE("Discarding plan with index %u (%s), not better than previous best plan", + indexId, + index->GetName().c_str()); + JitDestroyPlan((JitPlan*)nextPlan); + nextPlan = nullptr; + } + } + + if (plan == nullptr) { + MOT_LOG_TRACE("Failed to prepare Range DELETE plan"); + } + return (JitPlan*)plan; } @@ -1165,6 +1311,14 @@ static JitPlan* JitPrepareSimplePlan(Query* query) } else { plan = JitPrepareRangeSelectPlan(query, table, JoinClauseNone); } + } else if (query->commandType == CMD_DELETE) { + if (!CheckQueryAttributes( + query, false, false, false)) { // range delete does not expect sort clause or aggregate clause + MOT_LOG_TRACE( + "JitPrepareSimplePlan(): Disqualifying range delete query - Invalid query attributes"); + } else { + plan = JitPrepareRangeDeletePlan(query, table); + } } else { MOT_LOG_TRACE("JitPrepareSimplePlan(): Disqualifying unsupported range delete query"); } @@ -1205,6 +1359,26 @@ static bool isValidJoinedRangeScan(Query* query, JitRangeSelectPlan* scan_plan2, return true; } +static JitJoinType getJoinType(JoinType joinType) +{ + switch (joinType) { + case JOIN_INNER: + return JitJoinType::JIT_JOIN_INNER; + + case JOIN_LEFT: + return JitJoinType::JIT_JOIN_LEFT; + + case JOIN_FULL: + return JitJoinType::JIT_JOIN_FULL; + + case JOIN_RIGHT: + return JitJoinType::JIT_JOIN_RIGHT; + + default: + return JitJoinType::JIT_JOIN_INVALID; + } +} + static JitJoinScanType getJoinScanType(JitIndexScanType outer_scan_type, JitIndexScanType inner_scan_type) { JitJoinScanType scan_type = JIT_JOIN_SCAN_INVALID; @@ -1222,6 +1396,30 @@ static JitJoinScanType getJoinScanType(JitIndexScanType outer_scan_type, JitInde return scan_type; } +static bool SetJoinCommandType(JitJoinPlan* joinPlan) +{ + if (joinPlan->m_aggCount == 0) { + switch (joinPlan->_scan_type) { + case JIT_JOIN_SCAN_POINT: + joinPlan->_command_type = JIT_COMMAND_POINT_JOIN; + break; + + case JIT_JOIN_SCAN_OUTER_POINT: + case JIT_JOIN_SCAN_INNER_POINT: + case JIT_JOIN_SCAN_RANGE: + joinPlan->_command_type = JIT_COMMAND_RANGE_JOIN; + break; + + default: + MOT_LOG_TRACE("Invalid JOIN scan type %d", (int)joinPlan->_scan_type); + return false; + } + } else { + joinPlan->_command_type = JIT_COMMAND_AGGREGATE_JOIN; + } + return true; +} + static JitPlan* JitPrepareExplicitJoinPlan(Query* query) { JitPlan* plan = nullptr; @@ -1244,9 +1442,26 @@ static JitPlan* JitPrepareExplicitJoinPlan(Query* query) Oid relid1 = rte1->relid; Oid relid2 = rte2->relid; + // make sure we support the join type: + JitJoinType joinType = getJoinType(rte3->jointype); + if ((joinType != JitJoinType::JIT_JOIN_INNER) && (joinType != JitJoinType::JIT_JOIN_LEFT)) { + MOT_LOG_TRACE("Unsupported join type: %d", (int)joinType); + return nullptr; + } + // make sure that WHERE clause refers to full prefix in each table (even an empty one) - MOT::Table* table1 = MOT::GetTableManager()->GetTableByExternal(relid1); - MOT::Table* table2 = MOT::GetTableManager()->GetTableByExternal(relid2); + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); + MOT::Table* table1 = currTxn->GetTableByExternalId(relid1); + if (table1 == nullptr) { + MOT_LOG_TRACE("Cannot prepare JOIN plan with table %u %s: not MOT table", relid1, rte1->relname); + return nullptr; + } + MOT::Table* table2 = currTxn->GetTableByExternalId(relid2); + if (table2 == nullptr) { + MOT_LOG_TRACE("Cannot prepare JOIN plan with table %u %s: not MOT table", relid2, rte2->relname); + return nullptr; + } MOT_LOG_TRACE("Preparing an explicit JOIN plan on tables: %s, %s", table1->GetTableName().c_str(), @@ -1287,7 +1502,7 @@ static JitPlan* JitPrepareExplicitJoinPlan(Query* query) // verify that point join has no aggregate if ((scan_plan1->_index_scan._scan_type == JIT_INDEX_SCAN_POINT) && (scan_plan2->_index_scan._scan_type == JIT_INDEX_SCAN_POINT)) { - if (scan_plan1->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { + if (scan_plan1->m_aggCount > 0) { MOT_LOG_TRACE("Invalid aggregate specifier for point JOIN plan"); JitDestroyPlan((JitPlan*)scan_plan1); JitDestroyPlan((JitPlan*)scan_plan2); @@ -1295,6 +1510,16 @@ static JitPlan* JitPrepareExplicitJoinPlan(Query* query) } } + // aggregate and left/right outer joins are not supported + bool outerJoinWithAgg = ((joinType == JitJoinType::JIT_JOIN_LEFT || joinType == JitJoinType::JIT_JOIN_RIGHT) && + (scan_plan1->m_aggCount > 0 || scan_plan2->m_aggCount > 0)); + if (outerJoinWithAgg) { + MOT_LOG_TRACE("Joins left/right outer with aggregate function are not supported"); + JitDestroyPlan((JitPlan*)scan_plan1); + JitDestroyPlan((JitPlan*)scan_plan2); + return nullptr; + } + // now prepare the final join plan size_t alloc_size = sizeof(JitJoinPlan); JitJoinPlan* join_plan = (JitJoinPlan*)MOT::MemSessionAlloc(alloc_size); @@ -1309,13 +1534,21 @@ static JitPlan* JitPrepareExplicitJoinPlan(Query* query) errno_t erc = memset_s(join_plan, alloc_size, 0, alloc_size); securec_check(erc, "\0", "\0"); join_plan->_plan_type = JIT_PLAN_JOIN; + join_plan->_join_type = joinType; join_plan->_outer_scan = scan_plan1->_index_scan; join_plan->_inner_scan = scan_plan2->_index_scan; join_plan->_select_exprs = scan_plan1->_select_exprs; join_plan->_limit_count = scan_plan1->_limit_count; - join_plan->_aggregate = scan_plan1->_aggregate; + join_plan->m_aggregates = scan_plan1->m_aggregates; + join_plan->m_aggCount = scan_plan1->m_aggCount; + scan_plan1->m_aggregates = nullptr; + scan_plan1->m_aggCount = 0; join_plan->_scan_type = getJoinScanType(join_plan->_outer_scan._scan_type, join_plan->_inner_scan._scan_type); + if (!SetJoinCommandType(join_plan)) { + JitDestroyPlan((JitPlan*)join_plan); + join_plan = nullptr; + } // cleanup only second plan's select expressions freeSelectExprArray(&scan_plan2->_select_exprs); @@ -1337,8 +1570,18 @@ static JitPlan* JitPrepareImplicitJoinPlan(Query* query) RangeTblEntry* rte1 = (RangeTblEntry*)linitial(query->rtable); RangeTblEntry* rte2 = (RangeTblEntry*)lsecond(query->rtable); - MOT::Table* table1 = MOT::GetTableManager()->GetTableByExternal(rte1->relid); - MOT::Table* table2 = MOT::GetTableManager()->GetTableByExternal(rte2->relid); + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); + MOT::Table* table1 = currTxn->GetTableByExternalId(rte1->relid); + if (table1 == nullptr) { + MOT_LOG_TRACE("Cannot prepare JOIN plan with table %u %s: not MOT table", rte1->relid, rte1->relname); + return nullptr; + } + MOT::Table* table2 = currTxn->GetTableByExternalId(rte2->relid); + if (table2 == nullptr) { + MOT_LOG_TRACE("Cannot prepare JOIN plan with table %u %s: not MOT table", rte2->relid, rte2->relname); + return nullptr; + } MOT_LOG_TRACE("Preparing an implicit JOIN plan on tables: %s, %s", table1->GetTableName().c_str(), @@ -1351,8 +1594,9 @@ static JitPlan* JitPrepareImplicitJoinPlan(Query* query) MOT_LOG_TRACE("Failed to prepare a scan plan for JOIN query first table %s", table1->GetTableName().c_str()); return nullptr; } - JitRangeSelectPlan* scan_plan2 = - (JitRangeSelectPlan*)JitPrepareRangeSelectPlan(query, table2, JoinClauseImplicit); // use JOIN clause + // use JOIN clause ans skip outer scan search expressions + JitRangeSelectPlan* scan_plan2 = (JitRangeSelectPlan*)JitPrepareRangeSelectPlan( + query, table2, JoinClauseImplicit, &scan_plan1->_index_scan._search_exprs); if (scan_plan2 == nullptr) { MOT_LOG_TRACE("Failed to prepare a scan plan for JOIN query second table %s", table2->GetTableName().c_str()); JitDestroyPlan((JitPlan*)scan_plan1); @@ -1376,7 +1620,7 @@ static JitPlan* JitPrepareImplicitJoinPlan(Query* query) // verify that point join has no aggregate if ((scan_plan1->_index_scan._scan_type == JIT_INDEX_SCAN_POINT) && (scan_plan2->_index_scan._scan_type == JIT_INDEX_SCAN_POINT)) { - if (scan_plan1->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { + if (scan_plan1->m_aggCount > 0) { MOT_LOG_TRACE("Invalid aggregate specifier for point JOIN plan"); JitDestroyPlan((JitPlan*)scan_plan1); JitDestroyPlan((JitPlan*)scan_plan2); @@ -1402,9 +1646,16 @@ static JitPlan* JitPrepareImplicitJoinPlan(Query* query) join_plan->_inner_scan = scan_plan2->_index_scan; join_plan->_select_exprs = scan_plan1->_select_exprs; join_plan->_limit_count = scan_plan1->_limit_count; - join_plan->_aggregate = scan_plan1->_aggregate; + join_plan->m_aggregates = scan_plan1->m_aggregates; + join_plan->m_aggCount = scan_plan1->m_aggCount; + scan_plan1->m_aggregates = nullptr; + scan_plan1->m_aggCount = 0; join_plan->_scan_type = getJoinScanType(join_plan->_outer_scan._scan_type, join_plan->_inner_scan._scan_type); + if (!SetJoinCommandType(join_plan)) { + JitDestroyPlan((JitPlan*)join_plan); + join_plan = nullptr; + } // cleanup only second plan's select expressions freeSelectExprArray(&scan_plan2->_select_exprs); @@ -1448,8 +1699,7 @@ static SubLink* GetSingleSubLink(Query* query, MOT::Table* table) if (quals != nullptr) { SubLinkFetcher subLinkFetcher; MOT::Index* index = table->GetPrimaryIndex(); - bool result = visitSearchExpressions(query, table, index, (Expr*)&quals[0], true, &subLinkFetcher, false); - if (!result) { + if (!visitSearchExpressions(query, table, index, (Expr*)&quals[0], true, &subLinkFetcher, false)) { MOT_LOG_TRACE("Failed to fetch WHERE clause single sub-link node"); } result = subLinkFetcher.GetSubLink(); @@ -1531,7 +1781,7 @@ static bool IsSingleResultPlan(JitPlan* plan) result = true; } else if (rangeSelectPlan->_limit_count == 1) { result = true; - } else if (rangeSelectPlan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) { + } else if (rangeSelectPlan->m_aggCount == 1) { result = true; } } @@ -1570,8 +1820,10 @@ static JitPlan* JitPrepareCompoundPlan(Query* query, Query* subQuery) RangeTblEntry* rte = (RangeTblEntry*)linitial(query->rtable); RangeTblEntry* subQueryRte = (RangeTblEntry*)linitial(subQuery->rtable); - MOT::Table* table = MOT::GetTableManager()->GetTableByExternal(rte->relid); - MOT::Table* subQueryTable = MOT::GetTableManager()->GetTableByExternal(subQueryRte->relid); + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); + MOT::Table* table = currTxn->GetTableByExternalId(rte->relid); + MOT::Table* subQueryTable = currTxn->GetTableByExternalId(subQueryRte->relid); MOT_LOG_TRACE("Preparing a compound plan on tables: %s, %s", table->GetTableName().c_str(), @@ -1709,48 +1961,73 @@ static JitPlan* JitPrepareCompoundPlan(Query* query) return plan; } -extern JitPlan* JitPreparePlan(Query* query, const char* query_string) +JitPlan* JitPreparePlan(Query* query, const char* query_string) { JitPlan* plan = nullptr; MOT_LOG_TRACE("Preparing plan for query: %s", query_string); + uint64_t startTime = GetSysClock(); // we start by checking the number of tables involved - if (list_length(query->rtable) == 1) { - plan = JitPrepareSimplePlan(query); - // special case: a sub-query that evaluates to point query or single value aggregate, which in turn - // is used in a point-select outer query - if ((plan == nullptr) && query->hasSubLinks && (query->commandType == CMD_SELECT)) { - plan = JitPrepareCompoundPlan(query); + int tableCount = list_length(query->rtable); + if (tableCount == 0) { + plan = JitPrepareInvokePlan(query); + } else if (tableCount == 1) { + // special case: table is a function execution result + RangeTblEntry* tableEntry = (RangeTblEntry*)linitial(query->rtable); + if (tableEntry->rtekind == RTE_FUNCTION) { + plan = JitPrepareInvokePlan(query); + } else { + plan = JitPrepareSimplePlan(query); + // special case: a sub-query that evaluates to point query or single value aggregate, which in turn + // is used in a point-select outer query + if ((plan == nullptr) && query->hasSubLinks && (query->commandType == CMD_SELECT)) { + plan = JitPrepareCompoundPlan(query); + } } } else { - plan = JitPrepareJoinPlan(query); + if (query->commandType == CMD_SELECT) { + plan = JitPrepareJoinPlan(query); + } else { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying query - unsupported JOIN operation"); + } } + if (plan != nullptr) { + uint64_t endTime = GetSysClock(); + uint64_t planTimeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + MOT_LOG_TRACE("Plan time %" PRIu64 " micros for query: %s", planTimeMicros, query_string); + } return plan; } -extern bool JitPlanHasDistinct(JitPlan* plan) +bool JitPlanHasDistinct(JitPlan* plan) { bool result = false; if (plan->_plan_type == JIT_PLAN_RANGE_SCAN) { if (((JitRangeScanPlan*)plan)->_command_type == JIT_COMMAND_SELECT) { JitRangeSelectPlan* select_plan = (JitRangeSelectPlan*)plan; - if ((select_plan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) && select_plan->_aggregate._distinct) { - result = true; + for (int i = 0; i < select_plan->m_aggCount; ++i) { + if (select_plan->m_aggregates[i]._distinct) { + result = true; + break; + } } } } else if (plan->_plan_type == JIT_PLAN_JOIN) { JitJoinPlan* join_plan = (JitJoinPlan*)plan; - if ((join_plan->_aggregate._aggreaget_op != JIT_AGGREGATE_NONE) && join_plan->_aggregate._distinct) { - result = true; + for (int i = 0; i < join_plan->m_aggCount; ++i) { + if (join_plan->m_aggregates[i]._distinct) { + result = true; + break; + } } } return result; } -extern bool JitPlanHasSort(JitPlan* plan) +bool JitPlanHasSort(JitPlan* plan) { bool result = false; @@ -1807,6 +2084,15 @@ static void JitDestroyRangeUpdatePlan(JitRangeUpdatePlan* plan) static void JitDestroyRangeSelectPlan(JitRangeSelectPlan* plan) { freeSelectExprArray(&plan->_select_exprs); + if (plan->m_aggregates != nullptr) { + MOT::MemSessionFree(plan->m_aggregates); + plan->m_aggregates = nullptr; + } + + if (plan->m_nonNativeSortParams) { + DestroyJitNonNativeSortParams(plan->m_nonNativeSortParams, JIT_CONTEXT_LOCAL); + plan->m_nonNativeSortParams = nullptr; + } } static void JitDestroyRangeScanPlan(JitRangeScanPlan* plan) @@ -1839,9 +2125,9 @@ static void JitDestroyCompoundPlan(JitCompoundPlan* plan) MOT::MemSessionFree(plan); } -extern void JitDestroyPlan(JitPlan* plan) +void JitDestroyPlan(JitPlan* plan) { - if (plan != nullptr) { + if ((plan != nullptr) && (plan != MOT_READY_JIT_PLAN)) { switch (plan->_plan_type) { case JIT_PLAN_INSERT_QUERY: JitDestroyInsertPlan((JitInsertPlan*)plan); @@ -1863,10 +2149,39 @@ extern void JitDestroyPlan(JitPlan* plan) JitDestroyCompoundPlan((JitCompoundPlan*)plan); break; + case JIT_PLAN_FUNCTION: + JitDestroyFunctionPlan((JitFunctionPlan*)plan); + break; + + case JIT_PLAN_INVOKE: + JitDestroyInvokePlan((JitInvokePlan*)plan); + break; + case JIT_PLAN_INVALID: + // fall through default: break; } } } + +bool JitPlanHasFunctionPlan(JitPlan* plan, JitFunctionPlan** outPlan) +{ + JitFunctionPlan* functionPlan = nullptr; + if (plan->_plan_type == JIT_PLAN_FUNCTION) { + functionPlan = (JitFunctionPlan*)plan; + } else if (plan->_plan_type == JIT_PLAN_INVOKE) { + functionPlan = ((JitInvokePlan*)plan)->m_functionPlan; + } + + if (outPlan != nullptr) { + *outPlan = functionPlan; + } + + if (functionPlan == nullptr) { + return false; + } + + return true; +} } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan.h b/src/gausskernel/storage/mot/jit_exec/jit_plan.h index d1eb882ab..1e3fd1f6a 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_plan.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan.h @@ -46,13 +46,22 @@ enum JitPlanType { JIT_PLAN_JOIN, /** @var Plan for a query with sub-queries. */ - JIT_PLAN_COMPOUND + JIT_PLAN_COMPOUND, + + /** @var Plan for stored procedure execution. */ + JIT_PLAN_FUNCTION, + + /** @var Plan for invoking a stored procedure. */ + JIT_PLAN_INVOKE }; /** @struct The parent struct for all plans. */ struct JitPlan { /** @var The type of plan being used. */ JitPlanType _plan_type; + + /** @var The command type being used (select, update or delete). */ + JitCommandType _command_type; }; /** @struct Insert plan. */ @@ -60,6 +69,9 @@ struct JitInsertPlan { /** @var The type of plan being used (always @ref JIT_PLAN_INSERT_QUERY). */ JitPlanType _plan_type; + /** @var The command type being used (select, update or delete). */ + JitCommandType _command_type; + /** @var The table into which the tuple is to be inserted. */ MOT::Table* _table; @@ -126,7 +138,10 @@ struct JitRangeScanPlan { /** @var The type of plan being used (always @ref JIT_PLAN_RANGE_SCAN). */ JitPlanType _plan_type; - /** @var The command type being used (either @ref JIT_COMMAND_UPDATE or @ref JIT_COMMAND_SELECT). */ + /** + * @var The command type being used (@ref JIT_COMMAND_UPDATE, @ref JIT_COMMAND_SELECT, or + * @ref JIT_COMMAND_DELETE). + */ JitCommandType _command_type; /** @var Defines how to make the scan. */ @@ -148,7 +163,7 @@ struct JitRangeUpdatePlan { JitColumnExprArray _update_exprs; }; -/** @strut Plan for SELECT range queries. */ +/** @struct Plan for SELECT range queries. */ struct JitRangeSelectPlan { /** @var The type of plan being used (always @ref JIT_PLAN_RANGE_SCAN). */ JitPlanType _plan_type; @@ -165,8 +180,29 @@ struct JitRangeSelectPlan { /** @var Limit on number of rows returned to the user (zero for none). */ int _limit_count; - /** @var An aggregate function (if one is specified then only one select expression should exist). */ - JitAggregate _aggregate; + /** @var Aggregate count. */ + int m_aggCount; + + /** @var Aggregate functions (if one is specified then only one select expression should exist). */ + JitAggregate* m_aggregates; + + /** @var Required params for non native sort. */ + JitNonNativeSortParams* m_nonNativeSortParams; +}; + +/** @strut Plan for range DELETE queries. */ +struct JitRangeDeletePlan { + /** @var The type of plan being used (always @ref JIT_PLAN_RANGE_SCAN). */ + JitPlanType _plan_type; + + /** @var The command type being used (either @ref JIT_COMMAND_DELETE). */ + JitCommandType _command_type; + + /** @var Defines how to make the scan. */ + JitIndexScan _index_scan; + + /** @var Limit on number of rows deleted (zero for none). */ + int _limit_count; }; /** @strut Plan for JOIN queries. */ @@ -174,9 +210,15 @@ struct JitJoinPlan { /** @var The type of plan being used (always @ref JIT_PLAN_JOIN). */ JitPlanType _plan_type; // always JIT_PLAN_JOIN + /** @var The command type being used (select, update or delete). */ + JitCommandType _command_type; + /** @var The type of join scan. */ JitJoinScanType _scan_type; + /** @var The kind of join being performed. */ + JitJoinType _join_type; + /** @var Defines how to make the outer loop scan. */ JitIndexScan _outer_scan; @@ -186,14 +228,20 @@ struct JitJoinPlan { /** @var Array of expressions to join from outer loop into inner loop search. */ JitJoinExpr _join_exprs; + /** @var Filters imposed on the query involving both tables. */ + JitFilterArray _filters; + /** @var Array of expressions to copy to the result tuple. */ JitSelectExprArray _select_exprs; /** @var Limit on number of rows returned to the user (zero for none). */ int _limit_count; - /** @var An aggregate function (if one is specified then only one select expression should exist). */ - JitAggregate _aggregate; + /** @var Aggregate count. */ + int m_aggCount; + + /** @var Aggregate functions (if one is specified then only one select expression should exist). */ + JitAggregate* m_aggregates; }; /** @strut Plan for compound point query with a single sub-query. */ @@ -214,6 +262,112 @@ struct JitCompoundPlan { JitPlan** _sub_query_plans; }; +/** @struct Plan for calling a query from within a jitted stored procedure. */ +struct JitCallSitePlan { + /** @ var The plan for jittable query, otherwise null. */ + JitPlan* m_queryPlan; + + /** @var The SPI plan for the query (must be valid - all JTI plan expressions depend on it). */ + SPIPlanPtr m_spiPlan; + + /** @var The PL/PG SQL expression from which the query comes. */ + PLpgSQL_expr* m_expr; + + /** @var The index of the expression according to visitation order in JIT planning. */ + int m_exprIndex; + + /** @var Holds the command type for non-jittable sub-queries. */ + CmdType m_queryCmdType; + + /** @var Holds the query string for non-jittable sub-queries. */ + char* m_queryString; + + /** @var The actual number of parameters used by the sub-query. */ + int m_callParamCount; + + /** @var Denotes whether this is an unjittable invoke query. */ + uint8_t m_isUnjittableInvoke; + + /** @var Denotes whether this is a modifying statement. */ + uint8_t m_isModStmt; + + /** @var Denotes whether the query result should be put into local variable. */ + uint8_t m_isInto; + + /** @var Align next member to 8 bytes. */ + uint8_t m_padding; + + /** @var Information requires to form the parameter list used to invoke the query. */ + JitCallParamInfo* m_callParamInfo; + + /** @var The global tuple descriptor used to prepare tuple table slot object for the sub-query result. */ + TupleDesc m_tupDesc; +}; + +/** @strut Plan for invoke stored procedure. */ +struct JitFunctionPlan { + JitPlanType _plan_type; // always JIT_PLAN_FUNCTION + + /** @var The command type being used (select, update or delete). */ + JitCommandType _command_type; + + /** @var The executed function identifier in the system catalog. */ + Oid _function_id; + + /** @var The executed function name. */ + char* _function_name; + + /** @var The compiled function. */ + PLpgSQL_function* m_function; + + /** @var The list of parameter types passed to the function. */ + Oid* m_paramTypes; + + /** @var The number of parameters passed to the function (this includes all datums). */ + uint32_t m_paramCount; + + /** @var The number of arguments passed to the function (this includes only input arguments). */ + uint32_t m_argCount; + + /** @var The context of the jitted queries for each query in the stored procedure. */ + JitCallSitePlan* m_callSitePlanList; + + /** @var The number of sub-queries in the executed stored procedure. */ + int _query_count; +}; + +/** @strut Plan for invoke stored procedure. */ +struct JitInvokePlan { + JitPlanType _plan_type; // always JIT_PLAN_INVOKE + + /** @var The command type being used (select, update or delete). */ + JitCommandType _command_type; + + /** @var The executed function identifier in the system catalog. */ + Oid _function_id; + + /** @var The executed function name. */ + char* _function_name; + + /** @var The executed function xmin. */ + TransactionId m_functionTxnId; + + /** @var The context of the jitted function. */ + JitFunctionPlan* m_functionPlan; + + /** @var The arguments passed to the executed function. */ + JitExpr** _args; + + /** @var The number of arguments passed to the executed function. */ + int _arg_count; + + /** @var Default parameter expression array. */ + JitExpr** m_defaultParams; + + /** @var Default parameter count. */ + int m_defaultParamCount; +}; + /** @define A special constant denoting a plan is not needed since jitted query has already been generated. */ #define MOT_READY_JIT_PLAN ((JitPlan*)-1) @@ -225,6 +379,18 @@ struct JitCompoundPlan { */ extern JitPlan* JitPreparePlan(Query* query, const char* queryString); +/** + * @brief Prepares a lite execution plan for a stored procedure. This plan will be later used to generate jitted code. + * @param function The parsed stored procedure. + * @param functionSource The source code of the stored procedure. + * @param functionName The name of the stored procedure. + * @param functionOid The function identifier. + * @param isStrict Specified whether function is defined as strict (i.e. does not allow null parameters). + * @return The plan, or null of failed. + */ +extern JitPlan* JitPrepareFunctionPlan( + PLpgSQL_function* function, const char* functionSource, const char* functionName, Oid functionOid, bool isStrict); + /** @brief Queries whether a plan has a DISTINCT operator. */ extern bool JitPlanHasDistinct(JitPlan* plan); @@ -239,10 +405,29 @@ extern bool JitPlanHasSort(JitPlan* plan); extern void JitExplainPlan(Query* query, JitPlan* plan); /** - * @brief Releases all resources associated with a plan. - * @param plan The plan to destroy. + * @brief Explains how a plan is to be executed. + * @param function The parsed stored procedure for which the jitted plan is to be explained. + * @param plan The plan to be explained. */ -extern void JitDestroyPlan(JitPlan* plan); +extern void JitExplainPlan(PLpgSQL_function* function, JitPlan* plan); + +/** + * @brief Queries whether a query is acceptable for JIT. + * @param query The query to check. + * @param allowSorting Specifies whether sorting clause is allowed. + * @param allowAggregate Specifies whether aggregate clause is allowed. + * @param allowSublink Specifies whether sub-queries are allowed. + * @return True if the query is acceptable for JIT, otherwise false. + */ +extern bool CheckQueryAttributes(const Query* query, bool allowSorting, bool allowAggregate, bool allowSublink); + +/** + * @brief Queries whether a plan involves function plan. + * @param plan The plan to check. + * @param[out] outPlan The function plan. + * @return True if the given plan is an invoke or a function plan. + */ +extern bool JitPlanHasFunctionPlan(JitPlan* plan, JitFunctionPlan** outPlan); } // namespace JitExec #endif /* JIT_PLAN_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.cpp b/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.cpp index 038307f72..2696e4a36 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.cpp @@ -28,13 +28,19 @@ */ #include "global.h" #include "jit_plan_expr.h" +#include "catalog/pg_aggregate.h" +#include "mot_internal.h" namespace JitExec { -IMPLEMENT_CLASS_LOGGER(ExpressionVisitor, JitExec) DECLARE_LOGGER(JitPlanExpr, JitExec) -// Forward declarations -JitExpr* parseExpr(Query* query, Expr* expr, int arg_pos, int depth); +static void FreeScalarArrayOpExpr(JitScalarArrayOpExpr* expr); +static bool ExprHasVarRef(Expr* expr, int depth); + +bool ExprHasVarRef(Expr* expr) +{ + return ExprHasVarRef(expr, 0); +} bool ExpressionCounter::OnExpression( Expr* expr, int columnType, int tableColumnId, MOT::Table* table, JitWhereOperatorClass opClass, bool joinExpr) @@ -54,7 +60,7 @@ bool ExpressionCollector::OnExpression( MOT_LOG_TRACE("ExpressionCollector::onExpression(): Skipping non-equals operator"); return true; // this is not an error condition } else if (*_expr_count < _expr_array->_count) { - JitExpr* jit_expr = parseExpr(_query, expr, 0, 0); + JitExpr* jit_expr = parseExpr(_query, expr, 0); if (jit_expr == nullptr) { MOT_LOG_TRACE("ExpressionCollector::onExpression(): Failed to parse expression %d", *_expr_count); Cleanup(); @@ -82,6 +88,7 @@ void ExpressionCollector::Cleanup() bool RangeScanExpressionCollector::Init() { + bool result = false; _max_index_ops = _index->GetNumFields() + 1; size_t alloc_size = sizeof(IndexOpClass) * _max_index_ops; _index_ops = (IndexOpClass*)MOT::MemSessionAlloc(alloc_size); @@ -91,9 +98,10 @@ bool RangeScanExpressionCollector::Init() "Failed to allocate %u bytes for %d index operations", (unsigned)alloc_size, _max_index_ops); - return false; + } else { + result = true; } - return true; + return result; } bool RangeScanExpressionCollector::OnExpression( @@ -112,7 +120,7 @@ bool RangeScanExpressionCollector::OnExpression( _max_index_ops); return false; } else { - JitExpr* jit_expr = parseExpr(_query, expr, 0, 0); + JitExpr* jit_expr = parseExpr(_query, expr, 0); if (jit_expr == nullptr) { MOT_LOG_TRACE( "RangeScanExpressionCollector::onExpression(): Failed to parse expression %d", _index_op_count); @@ -124,8 +132,14 @@ bool RangeScanExpressionCollector::OnExpression( _index_scan->_search_exprs._exprs[_index_op_count]._expr = jit_expr; _index_scan->_search_exprs._exprs[_index_op_count]._column_type = columnType; _index_scan->_search_exprs._exprs[_index_op_count]._join_expr = joinExpr; - _index_ops[_index_op_count]._index_column_id = MapTableColumnToIndex(_table, _index, tableColumnId); + int indexColumnId = MapTableColumnToIndex(_table, _index, tableColumnId); + _index_scan->_search_exprs._exprs[_index_op_count]._index_column_id = indexColumnId; + _index_scan->_search_exprs._exprs[_index_op_count]._op_class = opClass; + _index_ops[_index_op_count]._index_column_id = indexColumnId; _index_ops[_index_op_count]._op_class = opClass; + MOT_LOG_TRACE("RangeScanExpressionCollector::onExpression(): collected index column %d at index %d", + _index_ops[_index_op_count]._index_column_id, + _index_op_count); ++_index_op_count; return true; } @@ -160,8 +174,22 @@ void RangeScanExpressionCollector::EvaluateScanType() // first step: sort in-place all collected operators if (_index_op_count > 1) { - MOT_LOG_TRACE("Sorting index ops") + for (int i = 0; i < _index_op_count; ++i) { + MOT_LOG_TRACE("Unsorted index column %d: id=%d, op=%d", + i, + _index_ops[i]._index_column_id, + (int)_index_ops[i]._op_class); + } + MOT_LOG_TRACE("Sorting index ops"); std::stable_sort(&_index_ops[0], &_index_ops[_index_op_count], IndexOpCmp); + std::stable_sort( + &_index_scan->_search_exprs._exprs[0], &_index_scan->_search_exprs._exprs[_index_op_count], ExprCmp(*this)); + for (int i = 0; i < _index_op_count; ++i) { + MOT_LOG_TRACE("Sorted index column %d: id=%d, op=%d", + i, + _index_ops[i]._index_column_id, + (int)_index_ops[i]._op_class); + } } // now verify all but last two are equals operator @@ -185,6 +213,11 @@ void RangeScanExpressionCollector::EvaluateScanType() if (!ScanHasHoles(scanType)) { _index_scan->_scan_type = scanType; _index_scan->_column_count = columnCount; + // clean up unused expressions before decreasing number of expressions + for (int i = _index_op_count; i < _index_scan->_search_exprs._count; ++i) { + freeExpr(_index_scan->_search_exprs._exprs[i]._expr); + _index_scan->_search_exprs._exprs[i]._expr = nullptr; + } _index_scan->_search_exprs._count = _index_op_count; // update real number of participating expressions } } @@ -192,23 +225,33 @@ void RangeScanExpressionCollector::EvaluateScanType() bool RangeScanExpressionCollector::DetermineScanType(JitIndexScanType& scanType, int& columnCount) { // now carefully inspect last two operators to determine expected scan type + bool result = true; if (_index_op_count >= 2) { if (_index_ops[_index_op_count - 2]._op_class == JIT_WOC_EQUALS) { if (_index_ops[_index_op_count - 1]._op_class == JIT_WOC_EQUALS) { if (_index->GetUnique() && (_index_op_count == _index->GetNumFields())) { + MOT_LOG_TRACE("DetermineScanType(): Found point query scan"); scanType = JIT_INDEX_SCAN_POINT; } else { + MOT_LOG_TRACE("DetermineScanType(): Found closed query scan"); scanType = JIT_INDEX_SCAN_CLOSED; } } else { + MOT_LOG_TRACE("DetermineScanType(): Found semi-open query scan"); scanType = JIT_INDEX_SCAN_SEMI_OPEN; _index_scan->_last_dim_op1 = _index_ops[_index_op_count - 1]._op_class; } } else if (_index_ops[_index_op_count - 1]._op_class == JIT_WOC_EQUALS) { - MOT_LOG_TRACE("RangeScanExpressionCollector(): Disqualifying query - invalid open scan specifying last " - "operator as equals, while previous one is not"); - return false; + // this can be treated as semi-open scan, having last expression regarded as filter + MOT_LOG_TRACE("RangeScanExpressionCollector(): scan specifying last operator as equals, while previous one " + "is not - regarding scan as semi-open with a filter"); + scanType = JIT_INDEX_SCAN_SEMI_OPEN; + _index_scan->_last_dim_op1 = _index_ops[_index_op_count - 2]._op_class; + // remove last index op (clean up takes place later, both on success and failure) + --_index_op_count; + columnCount = _index_op_count; } else { + MOT_LOG_TRACE("DetermineScanType(): Found open query scan"); scanType = JIT_INDEX_SCAN_OPEN; columnCount = _index_op_count - 1; _index_scan->_last_dim_op1 = _index_ops[_index_op_count - 2]._op_class; @@ -217,16 +260,19 @@ bool RangeScanExpressionCollector::DetermineScanType(JitIndexScanType& scanType, } else if (_index_op_count == 1) { if (_index_ops[0]._op_class == JIT_WOC_EQUALS) { if (_index->GetUnique() && (_index_op_count == _index->GetNumFields())) { + MOT_LOG_TRACE("DetermineScanType(): Found point query scan (one column)"); scanType = JIT_INDEX_SCAN_POINT; } else { + MOT_LOG_TRACE("DetermineScanType(): Found closed query scan (one column)"); scanType = JIT_INDEX_SCAN_CLOSED; } } else { + MOT_LOG_TRACE("DetermineScanType(): Found semi-open query scan (one column)"); scanType = JIT_INDEX_SCAN_SEMI_OPEN; _index_scan->_last_dim_op1 = _index_ops[0]._op_class; } } - return true; + return result; } void RangeScanExpressionCollector::Cleanup() @@ -234,6 +280,7 @@ void RangeScanExpressionCollector::Cleanup() for (int i = 0; i < _index_op_count; ++i) { if (_index_scan->_search_exprs._exprs[i]._expr != nullptr) { freeExpr(_index_scan->_search_exprs._exprs[i]._expr); + _index_scan->_search_exprs._exprs[i]._expr = nullptr; } } _index_scan->_search_exprs._count = 0; @@ -249,6 +296,39 @@ bool RangeScanExpressionCollector::RemoveDuplicates() return (result == 0); } +bool RangeScanExpressionCollector::IsSameOp(JitWhereOperatorClass lhs, JitWhereOperatorClass rhs) +{ + // test same operation + switch (lhs) { + case JIT_WOC_GREATER_EQUALS: + case JIT_WOC_GREATER_THAN: { + switch (rhs) { + case JIT_WOC_GREATER_EQUALS: + case JIT_WOC_GREATER_THAN: + return true; + default: + break; + } + break; + } + case JIT_WOC_LESS_EQUALS: + case JIT_WOC_LESS_THAN: { + switch (rhs) { + case JIT_WOC_LESS_EQUALS: + case JIT_WOC_LESS_THAN: + return true; + default: + break; + } + break; + } + default: + break; + } + + return false; +} + int RangeScanExpressionCollector::RemoveSingleDuplicate() { // scan and stop after first removal @@ -257,6 +337,11 @@ int RangeScanExpressionCollector::RemoveSingleDuplicate() if (_index_ops[i]._index_column_id == _index_ops[j]._index_column_id) { MOT_LOG_TRACE("RangeScanExpressionCollector(): Found duplicate column ref at %d and %d", i, j); if ((_index_ops[i]._op_class != JIT_WOC_EQUALS) && (_index_ops[j]._op_class != JIT_WOC_EQUALS)) { + // test same operation + if (IsSameOp(_index_ops[i]._op_class, _index_ops[j]._op_class)) { + MOT_LOG_TRACE("RangeScanExpressionCollector(): Found same operation - Disqualifying query"); + return -1; + } MOT_LOG_DEBUG("RangeScanExpressionCollector(): Skipping probable open scan operators while " "removing duplicates"); continue; @@ -345,6 +430,25 @@ bool RangeScanExpressionCollector::IndexOpCmp(const IndexOpClass& lhs, const Ind return result < 0; } +bool RangeScanExpressionCollector::ExprCmpImp(const JitColumnExpr& lhs, const JitColumnExpr& rhs) +{ + int lhsIndexColId = MapTableColumnToIndex(_table, _index, lhs._table_column_id); + int rhsIndexColId = MapTableColumnToIndex(_table, _index, rhs._table_column_id); + int result = IntCmp(lhsIndexColId, rhsIndexColId); + if (result == 0) { + // make sure equals appears before other operators in case column id is equal + if ((lhs._op_class == JIT_WOC_EQUALS) && (rhs._op_class != JIT_WOC_EQUALS)) { + result = -1; + } else if ((lhs._op_class != JIT_WOC_EQUALS) && (rhs._op_class == JIT_WOC_EQUALS)) { + result = 1; + } + // otherwise we keep order intact to avoid misinterpreting open range scan as inverted + } + + // we return true when strict ascending order is preserved + return result < 0; +} + bool RangeScanExpressionCollector::ScanHasHoles(JitIndexScanType scan_type) const { MOT_ASSERT(_index_op_count >= 1); @@ -395,42 +499,51 @@ bool RangeScanExpressionCollector::ScanHasHoles(JitIndexScanType scan_type) cons return false; } -bool FilterCollector::OnFilterExpr(int filterOp, int filterOpFuncId, Expr* lhs, Expr* rhs) +bool FilterCollector::OnFilterExpr(Expr* expr) { - JitExpr* jit_lhs = parseExpr(_query, lhs, 0, 0); - if (jit_lhs == nullptr) { - MOT_LOG_TRACE( - "FilterCollector::onFilterExpr(): Failed to parse LHS expression in filter expression %d", *_filter_count); + bool hasVarExpr = false; + JitExpr* filterExpr = parseExpr(_query, expr, 0, &hasVarExpr); + if (filterExpr == nullptr) { + MOT_LOG_TRACE("FilterCollector::onFilterExpr(): Failed to parse filter expression %d", *_filter_count); Cleanup(); return false; } - JitExpr* jit_rhs = parseExpr(_query, rhs, 1, 0); - if (jit_rhs == nullptr) { - MOT_LOG_TRACE( - "FilterCollector::onFilterExpr(): Failed to parse RHS expression in filter expression %d", *_filter_count); - freeExpr(jit_lhs); - Cleanup(); - return false; - } - if (*_filter_count < _filter_array->_filter_count) { - _filter_array->_scan_filters[*_filter_count]._filter_op = filterOp; - _filter_array->_scan_filters[*_filter_count]._filter_op_funcid = filterOpFuncId; - _filter_array->_scan_filters[*_filter_count]._lhs_operand = jit_lhs; - _filter_array->_scan_filters[*_filter_count]._rhs_operand = jit_rhs; - ++(*_filter_count); - return true; + bool isOneTimeFilter = !hasVarExpr; + if (isOneTimeFilter) { + if (*m_oneTimeFilterCount < m_oneTimeFilterArray->_filter_count) { + m_oneTimeFilterArray->_scan_filters[*m_oneTimeFilterCount] = filterExpr; + ++(*m_oneTimeFilterCount); + MOT_LOG_TRACE("FilterCollector::onFilterExpr():Collected one-time filter"); + return true; + } else { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Prepare JIT Plan", + "Exceeded one-time filter count %d", + m_oneTimeFilterArray->_filter_count); + return false; + } } else { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Prepare JIT Plan", "Exceeded filter count %d", _filter_array->_filter_count); - return false; + if (*_filter_count < _filter_array->_filter_count) { + _filter_array->_scan_filters[*_filter_count] = filterExpr; + ++(*_filter_count); + return true; + } else { + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Prepare JIT Plan", "Exceeded filter count %d", _filter_array->_filter_count); + return false; + } } } void FilterCollector::Cleanup() { for (int i = 0; i < *_filter_count; ++i) { - freeExpr(_filter_array->_scan_filters[*_filter_count]._lhs_operand); - freeExpr(_filter_array->_scan_filters[*_filter_count]._rhs_operand); + freeExpr(_filter_array->_scan_filters[i]); + _filter_array->_scan_filters[i] = nullptr; + } + for (int i = 0; i < *m_oneTimeFilterCount; ++i) { + freeExpr(m_oneTimeFilterArray->_scan_filters[i]); + m_oneTimeFilterArray->_scan_filters[i] = nullptr; } } @@ -455,8 +568,10 @@ bool SubLinkFetcher::OnExpression( MOT_LOG_TRACE("SubLinkFetcher::onExpression(): unsupported sub-link outer test expression"); return false; // unsupported sub-link type, we disqualify query } +#ifdef JIT_SUPPORT_FOR_SUB_LINK MOT_ASSERT(_subLink == nullptr); _subLink = subLink; +#endif } return true; } @@ -464,12 +579,14 @@ bool SubLinkFetcher::OnExpression( MOT::Table* getRealTable(const Query* query, int table_ref_id, int column_id) { MOT::Table* table = nullptr; + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); if (table_ref_id > list_length(query->rtable)) { // varno index is 1-based MOT_LOG_TRACE("getRealTable(): Invalid table reference id %d", table_ref_id); } else { RangeTblEntry* rte = (RangeTblEntry*)list_nth(query->rtable, table_ref_id - 1); if (rte->rtekind == RTE_RELATION) { - table = MOT::GetTableManager()->GetTableByExternal(rte->relid); + table = currTxn->GetTableByExternalId(rte->relid); if (table == nullptr) { MOT_LOG_TRACE("getRealTable(): Could not find table by external id %d", rte->relid); } @@ -480,7 +597,7 @@ MOT::Table* getRealTable(const Query* query, int table_ref_id, int column_id) MOT_LOG_TRACE("getRealTable(): Invalid indirect table ref index %d", table_ref_id); } else { rte = (RangeTblEntry*)list_nth(query->rtable, table_ref_id - 1); - table = MOT::GetTableManager()->GetTableByExternal(rte->relid); + table = currTxn->GetTableByExternalId(rte->relid); if (table == nullptr) { MOT_LOG_TRACE("getRealTable(): Could not find table by indirected external id %d", rte->relid); } @@ -535,7 +652,7 @@ int getRealColumnId(const Query* query, int table_ref_id, int column_id, const M return column_id; } -static JitExpr* parseConstExpr(const Const* const_expr, int arg_pos) +static JitExpr* parseConstExpr(const Const* const_expr) { if (!IsTypeSupported(const_expr->consttype)) { MOT_LOG_TRACE("Disqualifying constant expression: constant type %d is unsupported", (int)const_expr->consttype); @@ -550,14 +667,14 @@ static JitExpr* parseConstExpr(const Const* const_expr, int arg_pos) } else { result->_expr_type = JIT_EXPR_TYPE_CONST; result->_const_type = const_expr->consttype; + result->m_collationId = const_expr->constcollid; result->_value = const_expr->constvalue; result->_is_null = const_expr->constisnull; - result->_arg_pos = arg_pos; } return (JitExpr*)result; } -static JitExpr* parseParamExpr(const Param* param_expr, int arg_pos) +static JitExpr* parseParamExpr(const Param* param_expr) { if (!IsTypeSupported(param_expr->paramtype)) { MOT_LOG_TRACE( @@ -573,13 +690,13 @@ static JitExpr* parseParamExpr(const Param* param_expr, int arg_pos) } else { result->_expr_type = JIT_EXPR_TYPE_PARAM; result->_param_type = param_expr->paramtype; + result->m_collationId = param_expr->paramcollid; result->_param_id = param_expr->paramid - 1; // move to zero-based index - result->_arg_pos = arg_pos; } return (JitExpr*)result; } -static JitExpr* parseVarExpr(Query* query, const Var* var_expr, int arg_pos) +static JitExpr* parseVarExpr(Query* query, const Var* var_expr) { // make preliminary tests before memory allocation takes place if (!IsTypeSupported(var_expr->vartype)) { @@ -611,25 +728,28 @@ static JitExpr* parseVarExpr(Query* query, const Var* var_expr, int arg_pos) } else { result->_expr_type = JIT_EXPR_TYPE_VAR; result->_column_type = var_expr->vartype; + result->m_collationId = var_expr->varcollid; result->_table = table; result->_column_id = column_id; - result->_arg_pos = arg_pos; } return (JitExpr*)result; } -static JitExpr* parseRelabelExpr(Query* query, RelabelType* relabel_type, int arg_pos) +static JitExpr* parseRelabelExpr(Query* query, RelabelType* relabel_type, bool* hasVarExpr) { JitExpr* result = nullptr; if (relabel_type->arg->type == T_Param) { - result = parseParamExpr((Param*)relabel_type->arg, arg_pos); + result = parseParamExpr((Param*)relabel_type->arg); if (result != nullptr) { // replace result type with relabeled type ((JitParamExpr*)result)->_param_type = relabel_type->resulttype; } } else if (relabel_type->arg->type == T_Var) { - result = parseVarExpr(query, (Var*)relabel_type->arg, arg_pos); + if (hasVarExpr != nullptr) { + *hasVarExpr = true; + } + result = parseVarExpr(query, (Var*)relabel_type->arg); if (result != nullptr) { // replace result type with relabeled type ((JitVarExpr*)result)->_column_type = relabel_type->resulttype; @@ -648,6 +768,7 @@ static bool ValidateFuncCallExpr(Expr* expr) Oid funcId; List* args; Oid oidValue; + bool returnsSet = false; if (expr->type == T_OpExpr) { OpExpr* opExpr = (OpExpr*)expr; @@ -655,12 +776,14 @@ static bool ValidateFuncCallExpr(Expr* expr) funcId = opExpr->opfuncid; args = opExpr->args; oidValue = opExpr->opno; // with operator expression we prefer printing the operator id for easier lookup + returnsSet = opExpr->opretset; } else if (expr->type == T_FuncExpr) { FuncExpr* funcExpr = (FuncExpr*)expr; resultType = funcExpr->funcresulttype; funcId = funcExpr->funcid; args = funcExpr->args; oidValue = funcExpr->funcid; + returnsSet = funcExpr->funcretset; } else { MOT_LOG_TRACE("ValidateFuncOpExpr(): Invalid expression type %u", expr->type); return false; @@ -682,107 +805,136 @@ static bool ValidateFuncCallExpr(Expr* expr) return false; } + if (returnsSet) { + MOT_LOG_TRACE("Unsupported %s %u: expression returns set", exprName, oidValue); + return false; + } + return true; } -static JitExpr* parseOpExpr(Query* query, const OpExpr* op_expr, int arg_pos, int depth) +static JitExpr* parseOpExpr(Query* query, const OpExpr* op_expr, int depth, bool* hasVarExpr) { if (!ValidateFuncCallExpr((Expr*)op_expr)) { MOT_LOG_TRACE("Disqualifying invalid operator expression"); return nullptr; } - JitExpr* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; + int argCount = list_length(op_expr->args); + size_t allocSize = sizeof(JitExpr*) * argCount; + JitExpr** args = (JitExpr**)MOT::MemSessionAlloc(allocSize); + if (args == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Plan", + "Failed to allocate %u bytes for %d arguments in operator expression", + (unsigned)allocSize, + argCount); + return nullptr; + } int arg_num = 0; ListCell* lc = nullptr; foreach (lc, op_expr->args) { Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = parseExpr(query, sub_expr, arg_pos + arg_num, depth + 1); + args[arg_num] = parseExpr(query, sub_expr, depth + 1, hasVarExpr); if (args[arg_num] == nullptr) { MOT_LOG_TRACE("Failed to process operator sub-expression %d", arg_num); for (int i = 0; i < arg_num; ++i) { freeExpr(args[i]); } + MOT::MemSessionFree(args); return nullptr; } - if (++arg_num == MOT_JIT_MAX_FUNC_EXPR_ARGS) { - break; - } + ++arg_num; } + MOT_ASSERT(arg_num == argCount); - size_t alloc_size = sizeof(JitOpExpr); - JitOpExpr* result = (JitOpExpr*)MOT::MemSessionAlloc(alloc_size); + allocSize = sizeof(JitOpExpr); + JitOpExpr* result = (JitOpExpr*)MOT::MemSessionAlloc(allocSize); if (result == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for operator expression", alloc_size); + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Plan", + "Failed to allocate %u bytes for operator expression", + (unsigned)allocSize); for (int i = 0; i < arg_num; ++i) { freeExpr(args[i]); } + MOT::MemSessionFree(args); return nullptr; } result->_expr_type = JIT_EXPR_TYPE_OP; result->_op_no = op_expr->opno; result->_op_func_id = op_expr->opfuncid; + result->m_collationId = op_expr->opcollid; + result->m_opCollationId = op_expr->inputcollid; result->_result_type = op_expr->opresulttype; result->_arg_count = arg_num; - for (int i = 0; i < arg_num; ++i) { - result->_args[i] = args[i]; - } - result->_arg_pos = arg_pos; + result->_args = args; return (JitExpr*)result; } -static JitExpr* parseFuncExpr(Query* query, const FuncExpr* func_expr, int arg_pos, int depth) +static JitExpr* parseFuncExpr(Query* query, const FuncExpr* func_expr, int depth, bool* hasVarExpr) { if (!ValidateFuncCallExpr((Expr*)func_expr)) { MOT_LOG_TRACE("Disqualifying invalid function expression"); return nullptr; } - JitExpr* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; + int argCount = list_length(func_expr->args); + size_t allocSize = sizeof(JitExpr*) * argCount; + JitExpr** args = (JitExpr**)MOT::MemSessionAlloc(allocSize); + if (args == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Plan", + "Failed to allocate %u bytes for %d arguments in function expression", + (unsigned)allocSize, + argCount); + return nullptr; + } int arg_num = 0; ListCell* lc = nullptr; foreach (lc, func_expr->args) { Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = parseExpr(query, sub_expr, arg_pos + arg_num, depth + 1); + args[arg_num] = parseExpr(query, sub_expr, depth + 1, hasVarExpr); if (args[arg_num] == nullptr) { MOT_LOG_TRACE("Failed to process function sub-expression %d", arg_num); for (int i = 0; i < arg_num; ++i) { freeExpr(args[i]); } + MOT::MemSessionFree(args); return nullptr; } - if (++arg_num == MOT_JIT_MAX_FUNC_EXPR_ARGS) { - break; - } + ++arg_num; } + MOT_ASSERT(arg_num == argCount); - size_t alloc_size = sizeof(JitFuncExpr); - JitFuncExpr* result = (JitFuncExpr*)MOT::MemSessionAlloc(alloc_size); + allocSize = sizeof(JitFuncExpr); + JitFuncExpr* result = (JitFuncExpr*)MOT::MemSessionAlloc(allocSize); if (result == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for operator expression", alloc_size); + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Plan", + "Failed to allocate %u bytes for function expression", + (unsigned)allocSize); for (int i = 0; i < arg_num; ++i) { freeExpr(args[i]); } + MOT::MemSessionFree(args); return nullptr; } result->_expr_type = JIT_EXPR_TYPE_FUNC; result->_func_id = func_expr->funcid; + result->m_collationId = func_expr->funccollid; + result->m_funcCollationId = func_expr->inputcollid; result->_result_type = func_expr->funcresulttype; result->_arg_count = arg_num; - for (int i = 0; i < arg_num; ++i) { - result->_args[i] = args[i]; - } - result->_arg_pos = arg_pos; + result->_args = args; return (JitExpr*)result; } -static JitExpr* ParseSubLink(Query* query, const SubLink* subLink, int argPos, int depth) +static JitExpr* ParseSubLink(Query* query, const SubLink* subLink, int depth) { Query* subQuery = (Query*)subLink->subselect; @@ -813,14 +965,13 @@ static JitExpr* ParseSubLink(Query* query, const SubLink* subLink, int argPos, i result->_expr_type = JIT_EXPR_TYPE_SUBLINK; result->_source_expr = (Expr*)subLink; result->_result_type = resultType; - result->_arg_pos = argPos; result->_sub_query_index = 0; // currently the only valid value for a single sub-query } return (JitExpr*)result; } -static JitExpr* ParseBoolExpr(Query* query, const BoolExpr* boolExpr, int argPos, int depth) +static JitExpr* ParseBoolExpr(Query* query, const BoolExpr* boolExpr, int depth, bool* hasVarExpr) { if (list_length(boolExpr->args) > MOT_JIT_MAX_BOOL_EXPR_ARGS) { MOT_LOG_TRACE("Unsupported Boolean operator: too many arguments"); @@ -833,7 +984,7 @@ static JitExpr* ParseBoolExpr(Query* query, const BoolExpr* boolExpr, int argPos ListCell* lc = nullptr; foreach (lc, boolExpr->args) { Expr* subExpr = (Expr*)lfirst(lc); - args[argNum] = parseExpr(query, subExpr, argPos + argNum, depth + 1); + args[argNum] = parseExpr(query, subExpr, depth + 1, hasVarExpr); if (args[argNum] == nullptr) { MOT_LOG_TRACE("Failed to process operator sub-expression %d", argNum); for (int i = 0; i < argNum; ++i) { @@ -850,7 +1001,7 @@ static JitExpr* ParseBoolExpr(Query* query, const BoolExpr* boolExpr, int argPos JitBoolExpr* result = (JitBoolExpr*)MOT::MemSessionAlloc(allocSize); if (result == nullptr) { MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for boolean expression", allocSize); + MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for Boolean expression", allocSize); for (int i = 0; i < argNum; ++i) { freeExpr(args[i]); } @@ -859,18 +1010,244 @@ static JitExpr* ParseBoolExpr(Query* query, const BoolExpr* boolExpr, int argPos result->_expr_type = JIT_EXPR_TYPE_BOOL; result->_source_expr = (Expr*)boolExpr; + result->m_collationId = InvalidOid; result->_result_type = BOOLOID; result->_bool_expr_type = boolExpr->boolop; result->_arg_count = argNum; for (int i = 0; i < argNum; ++i) { result->_args[i] = args[i]; } - result->_arg_pos = argPos; return (JitExpr*)result; } -JitExpr* parseExpr(Query* query, Expr* expr, int arg_pos, int depth) +static JitExpr* ParseScalarArrayOpExpr(Query* query, const ScalarArrayOpExpr* expr, int depth, bool* hasVarExpr) +{ + if (list_length(expr->args) > MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("Unsupported Scalar Array operator: too many arguments"); + return nullptr; + } + + JitExpr* scalarExpr = parseExpr(query, (Expr*)linitial(expr->args), depth + 1, hasVarExpr); + if (scalarExpr == nullptr) { + MOT_LOG_TRACE("Failed to parse scalar expression"); + return nullptr; + } + Expr* arrayExpr = (Expr*)lsecond(expr->args); + if (arrayExpr->type != T_ArrayExpr) { + MOT_LOG_TRACE("Unexpected expr type for array argument: %d", (int)arrayExpr->type); + freeExpr(scalarExpr); + return nullptr; + } + ArrayExpr* arr = (ArrayExpr*)arrayExpr; + if (arr->multidims) { + MOT_LOG_TRACE("Unsupported multi-dimensional array"); + freeExpr(scalarExpr); + return nullptr; + } + if (!IsTypeSupported(arr->element_typeid)) { + MOT_LOG_TRACE("Unsupported array element type %u in Scalar Array Op", (unsigned)arr->element_typeid); + freeExpr(scalarExpr); + return nullptr; + } + + size_t allocSize = sizeof(JitScalarArrayOpExpr); + JitScalarArrayOpExpr* result = (JitScalarArrayOpExpr*)MOT::MemSessionAlloc(allocSize); + if (result == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for Scalar array expression", allocSize); + freeExpr(scalarExpr); + return nullptr; + } + + result->_expr_type = JIT_EXPR_TYPE_SCALAR_ARRAY_OP; + result->_source_expr = (Expr*)expr; + result->_result_type = BOOLOID; + result->m_collationId = expr->inputcollid; + result->m_funcCollationId = expr->inputcollid; + result->_op_no = expr->opno; + result->_op_func_id = expr->opfuncid; + result->m_useOr = expr->useOr; + result->m_scalar = scalarExpr; + result->m_arraySize = list_length(arr->elements); + if (result->m_arraySize <= 0) { + result->m_arraySize = 0; + result->m_arrayElements = nullptr; + } else { + allocSize = sizeof(JitExpr*) * result->m_arraySize; + result->m_arrayElements = (JitExpr**)MOT::MemSessionAlloc(allocSize); + if (result->m_arrayElements == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Plan", + "Failed to allocate %u bytes for %u Scalar array elements", + allocSize, + result->m_arrayElements); + FreeScalarArrayOpExpr(result); + return nullptr; + } else { + errno_t erc = memset_s(result->m_arrayElements, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + int elementIndex = 0; + ListCell* arg = nullptr; + foreach (arg, arr->elements) { + MOT_ASSERT(elementIndex < result->m_arraySize); + Expr* e = (Expr*)lfirst(arg); + result->m_arrayElements[elementIndex] = parseExpr(query, e, depth, hasVarExpr); + if (result->m_arrayElements[elementIndex] == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Prepare JIT Plan", + "Failed to parse array element %d in Scalar array elements", + elementIndex); + FreeScalarArrayOpExpr(result); + return nullptr; + } + ++elementIndex; + } + } + } + + return (JitExpr*)result; +} + +static JitExpr* ParseCoerceViaIOExpr(Query* query, const CoerceViaIO* expr, int depth, bool* hasVarExpr) +{ + JitExpr* arg = parseExpr(query, expr->arg, depth + 1, hasVarExpr); + if (arg == nullptr) { + MOT_LOG_TRACE("Failed to parse coerce via IO input expression"); + return nullptr; + } + + size_t allocSize = sizeof(JitCoerceViaIOExpr); + JitCoerceViaIOExpr* result = (JitCoerceViaIOExpr*)MOT::MemSessionAlloc(allocSize); + if (result == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "Prepare JIT Plan", "Failed to allocate %u bytes for Coerce via IO expression", allocSize); + freeExpr(arg); + return nullptr; + } + + result->_expr_type = JIT_EXPR_TYPE_COERCE_VIA_IO; + result->_source_expr = (Expr*)expr; + result->_result_type = expr->resulttype; + result->m_collationId = expr->resultcollid; + result->m_arg = arg; + + return (JitExpr*)result; +} + +static bool RelabelHasVarRef(RelabelType* relabel_type) +{ + if (relabel_type->arg->type == T_Var) { + return true; + } + + return false; +} + +static bool OpExprHasVarRef(const OpExpr* op_expr, int depth) +{ + ListCell* lc = nullptr; + foreach (lc, op_expr->args) { + Expr* sub_expr = (Expr*)lfirst(lc); + if (ExprHasVarRef(sub_expr, depth + 1)) { + return true; + } + } + + return false; +} + +static bool FuncExprHasVarRef(const FuncExpr* func_expr, int depth) +{ + ListCell* lc = nullptr; + foreach (lc, func_expr->args) { + Expr* sub_expr = (Expr*)lfirst(lc); + if (ExprHasVarRef(sub_expr, depth + 1)) { + return true; + } + } + + return false; +} + +static bool BoolExprHasVarRef(const BoolExpr* boolExpr, int depth) +{ + if (list_length(boolExpr->args) > MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("Unsupported Boolean operator: too many arguments"); + return false; + } + + ListCell* lc = nullptr; + foreach (lc, boolExpr->args) { + Expr* subExpr = (Expr*)lfirst(lc); + if (ExprHasVarRef(subExpr, depth + 1)) { + return true; + } + } + + return false; +} + +static bool ScalarArrayOpExprHasVarRef(const ScalarArrayOpExpr* expr, int depth) +{ + if (list_length(expr->args) > MOT_JIT_MAX_BOOL_EXPR_ARGS) { + MOT_LOG_TRACE("Unsupported Scalar Array operator: too many arguments"); + return false; + } + + if (ExprHasVarRef((Expr*)linitial(expr->args), depth + 1)) { + return true; + } + Expr* arrayExpr = (Expr*)lsecond(expr->args); + if (arrayExpr->type != T_ArrayExpr) { + MOT_LOG_TRACE("Unexpected expression type for array argument: %d", (int)arrayExpr->type); + return false; + } + ArrayExpr* arr = (ArrayExpr*)arrayExpr; + if (arr->multidims) { + MOT_LOG_TRACE("Unsupported multi-dimensional array"); + return false; + } + + ListCell* arg = nullptr; + foreach (arg, arr->elements) { + Expr* e = (Expr*)lfirst(arg); + if (ExprHasVarRef(e, depth)) { + return true; + } + } + + return false; +} + +static bool ExprHasVarRef(Expr* expr, int depth) +{ + if (depth > MOT_JIT_MAX_EXPR_DEPTH) { + MOT_LOG_TRACE( + "Cannot check for var expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); + return false; + } + + bool result = false; + if (expr->type == T_Var) { + result = true; + } else if (expr->type == T_RelabelType) { + result = RelabelHasVarRef((RelabelType*)expr); + } else if (expr->type == T_OpExpr) { + result = OpExprHasVarRef((OpExpr*)expr, depth); + } else if (expr->type == T_FuncExpr) { + result = FuncExprHasVarRef((FuncExpr*)expr, depth); + } else if (expr->type == T_BoolExpr) { + result = BoolExprHasVarRef((BoolExpr*)expr, depth); + } else if (expr->type == T_ScalarArrayOpExpr) { + result = ScalarArrayOpExprHasVarRef((ScalarArrayOpExpr*)expr, depth); + } + + return result; +} + +JitExpr* parseExpr(Query* query, Expr* expr, int depth, bool* hasVarExpr /* = nullptr */) { JitExpr* result = nullptr; @@ -880,21 +1257,28 @@ JitExpr* parseExpr(Query* query, Expr* expr, int arg_pos, int depth) } if (expr->type == T_Const) { - result = parseConstExpr((Const*)expr, arg_pos); + result = parseConstExpr((Const*)expr); } else if (expr->type == T_Param) { - result = parseParamExpr((Param*)expr, arg_pos); + result = parseParamExpr((Param*)expr); } else if (expr->type == T_Var) { - result = parseVarExpr(query, (Var*)expr, arg_pos); + if (hasVarExpr != nullptr) { + *hasVarExpr = true; + } + result = parseVarExpr(query, (Var*)expr); } else if (expr->type == T_RelabelType) { - result = parseRelabelExpr(query, (RelabelType*)expr, arg_pos); + result = parseRelabelExpr(query, (RelabelType*)expr, hasVarExpr); } else if (expr->type == T_OpExpr) { - result = parseOpExpr(query, (OpExpr*)expr, arg_pos, depth); + result = parseOpExpr(query, (OpExpr*)expr, depth, hasVarExpr); } else if (expr->type == T_FuncExpr) { - result = parseFuncExpr(query, (FuncExpr*)expr, arg_pos, depth); + result = parseFuncExpr(query, (FuncExpr*)expr, depth, hasVarExpr); } else if (expr->type == T_SubLink) { - result = ParseSubLink(query, (SubLink*)expr, arg_pos, depth); + result = ParseSubLink(query, (SubLink*)expr, depth); } else if (expr->type == T_BoolExpr) { - result = ParseBoolExpr(query, (BoolExpr*)expr, arg_pos, depth); + result = ParseBoolExpr(query, (BoolExpr*)expr, depth, hasVarExpr); + } else if (expr->type == T_ScalarArrayOpExpr) { + result = ParseScalarArrayOpExpr(query, (ScalarArrayOpExpr*)expr, depth, hasVarExpr); + } else if (expr->type == T_CoerceViaIO) { + result = ParseCoerceViaIOExpr(query, (CoerceViaIO*)expr, depth, hasVarExpr); } else { MOT_LOG_TRACE("Disqualifying expression: unsupported target expression type %d", (int)expr->type); } @@ -910,6 +1294,8 @@ static void freeOpExpr(JitOpExpr* op_expr) for (int i = 0; i < op_expr->_arg_count; ++i) { freeExpr(op_expr->_args[i]); } + MOT::MemSessionFree(op_expr->_args); + op_expr->_args = nullptr; } static void freeFuncExpr(JitFuncExpr* func_expr) @@ -917,6 +1303,25 @@ static void freeFuncExpr(JitFuncExpr* func_expr) for (int i = 0; i < func_expr->_arg_count; ++i) { freeExpr(func_expr->_args[i]); } + MOT::MemSessionFree(func_expr->_args); + func_expr->_args = nullptr; +} + +static void FreeScalarArrayOpExpr(JitScalarArrayOpExpr* expr) +{ + if (expr != nullptr) { + if (expr->m_scalar != nullptr) { + freeExpr(expr->m_scalar); + } + if (expr->m_arrayElements != nullptr) { + for (int i = 0; i < expr->m_arraySize; ++i) { + if (expr->m_arrayElements[i] != nullptr) { + freeExpr(expr->m_arrayElements[i]); + } + } + MOT::MemSessionFree(expr->m_arrayElements); + } + } } void freeExpr(JitExpr* expr) @@ -931,6 +1336,10 @@ void freeExpr(JitExpr* expr) freeFuncExpr((JitFuncExpr*)expr); break; + case JIT_EXPR_TYPE_SCALAR_ARRAY_OP: + FreeScalarArrayOpExpr((JitScalarArrayOpExpr*)expr); + break; + default: break; } @@ -1121,6 +1530,10 @@ static TableExprClass classifyTableExpr(Query* query, MOT::Table* table, MOT::In MOT_LOG_TRACE("classifyTableExpr(): neutral sub-query"); return TableExprNeutral; + case T_ScalarArrayOpExpr: + MOT_LOG_TRACE("classifyTableExpr(): scalar array operation treated as filter"); + return TableExprFilter; + default: MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Prepare JIT Plan", @@ -1131,7 +1544,7 @@ static TableExprClass classifyTableExpr(Query* query, MOT::Table* table, MOT::In } static bool VisitSearchOpExpressionFilters(Query* query, MOT::Table* table, MOT::Index* index, OpExpr* op_expr, - ExpressionVisitor* visitor, JitColumnExprArray* pkey_exprs) + ExpressionVisitor* visitor, bool include_join_exprs, JitColumnExprArray* pkey_exprs) { // when collecting filters we need to visit only those expressions not visited during pkey collection, but still // refer only to this table in addition, the where operator class is not EQUALS, then this is definitely a filter @@ -1157,22 +1570,23 @@ static bool VisitSearchOpExpressionFilters(Query* query, MOT::Table* table, MOT: TableExprClass tec = combineTableExprClass(lhs_tec, rhs_tec); if (tec == TableExprError) { MOT_LOG_TRACE( - "visitSearchOpExpression(): Encountered error while classifying table expressions for filters"); + "visitSearchOpExpression(): Encountered error while classifying table expression for filters"); return false; - } else if (tec == TableExprNeutral) { - MOT_LOG_TRACE("visitSearchOpExpression(): Skipping neutral table expressions for filters"); - } else if (tec == TableExprInvalid) { - MOT_LOG_TRACE("visitSearchOpExpression(): Skipping another table expressions for filters"); - } else if (tec == TableExprPKey) { - MOT_LOG_TRACE("visitSearchOpExpression(): Skipping primary key table expressions for filters"); + } else if ((tec == TableExprInvalid) && !include_join_exprs) { + MOT_LOG_TRACE("visitSearchOpExpression(): Skipping another table expression for filters (join " + "expressions excluded)"); } else { + if (tec == TableExprPKey) { + MOT_LOG_TRACE( + "visitSearchOpExpression(): Collecting duplicate primary key table expression for filters"); + } if (op_expr->opresulttype != BOOLOID) { MOT_LOG_TRACE( "visitSearchOpExpression(): Disqualifying query - filter result type %d is unsupported", op_expr->opresulttype); } else { MOT_LOG_TRACE("visitSearchOpExpression(): Collecting filter expression %p", op_expr); - if (!visitor->OnFilterExpr(op_expr->opno, op_expr->opfuncid, lhs, rhs)) { + if (!visitor->OnFilterExpr((Expr*)op_expr)) { MOT_LOG_TRACE("visitSearchOpExpression(): Expression collection failed"); return false; } @@ -1213,11 +1627,28 @@ static bool visitSearchOpExpression(Query* query, MOT::Table* table, MOT::Index* } if (!IsWhereOperatorSupported(op_expr->opno)) { - MOT_LOG_TRACE("visitSearchOpExpression(): Unsupported operator %d", op_expr->opno); - return false; + if (pkey_exprs == nullptr) { // first round, collecting pkey expressions + MOT_LOG_TRACE("visitSearchOpExpression(): Unsupported operator %u. First validation.", op_expr->opno); + // this is ok, we wait for second round to collect it as filter + return true; + } else { // second round, try to collect expression as filter + MOT_LOG_TRACE("visitSearchOpExpression(): Unsupported operator %u. Second validation. Validate filters " + "before failing", + op_expr->opno); + if (!VisitSearchOpExpressionFilters( + query, table, index, op_expr, visitor, include_join_exprs, pkey_exprs)) { + MOT_LOG_TRACE("visitSearchOpExpression(): Encountered error while classifying table expressions for " + "filters while op is not supported"); + return false; + } + MOT_LOG_TRACE("visitSearchOpExpression(): Unsupported operator %u. Second validation. Filter validation " + "passed successfully", + op_expr->opno); + return true; + } } - if (!VisitSearchOpExpressionFilters(query, table, index, op_expr, visitor, pkey_exprs)) { + if (!VisitSearchOpExpressionFilters(query, table, index, op_expr, visitor, include_join_exprs, pkey_exprs)) { MOT_LOG_TRACE("visitSearchOpExpression(): Encountered error while classifying table expressions for filters"); return false; } @@ -1299,7 +1730,9 @@ static bool visitSearchOpExpression(Query* query, MOT::Table* table, MOT::Index* } // last option: complex filter referring some table columns, so we need to analyze it is a valid filter expression - if (classifyTableExpr(query, table, index, (Expr*)op_expr) == TableExprFilter) { + // attention: we treat neutral expressions as filters + TableExprClass exprClass = classifyTableExpr(query, table, index, (Expr*)op_expr); + if ((exprClass == TableExprFilter) || (exprClass == TableExprNeutral)) { MOT_LOG_TRACE("visitSearchOpExpression(): Enabling complex filter expression %p", op_expr); return true; } @@ -1342,6 +1775,8 @@ bool visitSearchExpressions(Query* query, MOT::Table* table, MOT::Index* index, } else if (expr->type == T_BoolExpr) { result = visitSearchBoolExpression( query, table, index, (BoolExpr*)expr, include_pkey, visitor, include_join_exprs, pkey_exprs); + } else if (expr->type == T_ScalarArrayOpExpr) { + result = visitor->OnFilterExpr(expr); } else { MOT_LOG_TRACE("Unsupported expression type %d while visiting search expressions", (int)expr->type); } @@ -1374,7 +1809,7 @@ bool getSearchExpressions(Query* query, MOT::Table* table, MOT::Index* index, bo return result; } -static Node* getJoinQualifiers(const Query* query) +Node* getJoinQualifiers(const Query* query) { Node* quals = nullptr; @@ -1453,6 +1888,8 @@ bool getTargetExpressions(Query* query, JitColumnExprArray* target_exprs) int i = 0; ListCell* lc = nullptr; + MOT::TxnManager* currTxn = GetSafeTxn(__FUNCTION__); + MOT_ASSERT(currTxn != nullptr); foreach (lc, query->targetList) { TargetEntry* target_entry = (TargetEntry*)lfirst(lc); if (target_entry->resjunk) { @@ -1460,14 +1897,14 @@ bool getTargetExpressions(Query* query, JitColumnExprArray* target_exprs) continue; } if (i < target_exprs->_count) { - target_exprs->_exprs[i]._expr = parseExpr(query, target_entry->expr, 0, 0); + target_exprs->_exprs[i]._expr = parseExpr(query, target_entry->expr, 0); if (target_exprs->_exprs[i]._expr == nullptr) { MOT_LOG_TRACE("getTargetExpressions(): Failed to parse target expression %d", i); return false; } target_exprs->_exprs[i]._table_column_id = target_entry->resno; // update/insert if (target_entry->resorigtbl != 0) { // happens usually in INSERT - target_exprs->_exprs[i]._table = MOT::GetTableManager()->GetTableByExternal(target_entry->resorigtbl); + target_exprs->_exprs[i]._table = currTxn->GetTableByExternalId(target_entry->resorigtbl); if (target_exprs->_exprs[i]._table == nullptr) { MOT_LOG_TRACE("getTargetExpressions(): Failed to retrieve real table by id %d", (int)target_entry->resorigtbl); @@ -1476,7 +1913,7 @@ bool getTargetExpressions(Query* query, JitColumnExprArray* target_exprs) } else { // this is usually an update, and we retrieve the first table in rtable list RangeTblEntry* rte = (RangeTblEntry*)linitial(query->rtable); - target_exprs->_exprs[i]._table = MOT::GetTableManager()->GetTableByExternal(rte->relid); + target_exprs->_exprs[i]._table = currTxn->GetTableByExternalId(rte->relid); if (target_exprs->_exprs[i]._table == nullptr) { MOT_LOG_TRACE( "getTargetExpressions(): Failed to retrieve real table by inferred id %d", (int)rte->relid); @@ -1485,6 +1922,12 @@ bool getTargetExpressions(Query* query, JitColumnExprArray* target_exprs) } target_exprs->_exprs[i]._column_type = target_exprs->_exprs[i]._expr->_result_type; target_exprs->_exprs[i]._join_expr = false; + if (query->commandType == CMD_UPDATE) { + if (MOTAdaptor::IsColumnIndexed(target_entry->resno, target_exprs->_exprs[i]._table)) { + MOT_LOG_TRACE("getTargetExpressions(): Failed, column %d is used by index", target_entry->resno); + return false; + } + } ++i; } else { // this is unexpected and indicates internal error (we should have had enough items in the target expression @@ -1511,16 +1954,12 @@ bool getSelectExpressions(Query* query, JitSelectExprArray* select_exprs) continue; } if (i < select_exprs->_count) { - JitExpr* sub_expr = parseExpr(query, target_entry->expr, 0, 0); + JitExpr* sub_expr = parseExpr(query, target_entry->expr, 0); if (sub_expr == nullptr) { MOT_LOG_TRACE("getSelectExpressions(): Failed to parse select expression %d", i); return false; } - if (sub_expr->_expr_type != JIT_EXPR_TYPE_VAR) { - MOT_LOG_TRACE("getSelectExpressions(): Unexpected non-var expression"); - return false; - } - select_exprs->_exprs[i]._column_expr = (JitVarExpr*)sub_expr; + select_exprs->_exprs[i]._expr = sub_expr; select_exprs->_exprs[i]._tuple_column_id = target_entry->resno - 1; ++i; } else { @@ -1535,4 +1974,303 @@ bool getSelectExpressions(Query* query, JitSelectExprArray* select_exprs) } return true; } + +static int evalConstExpr(Expr* expr) +{ + int result = -1; + + // we expect to see either a Const or a cast to int8 of a Const + Const* const_expr = nullptr; + if (expr->type == T_Const) { + MOT_LOG_TRACE("evalConstExpr(): Found direct const expression"); + const_expr = (Const*)expr; + } else if (expr->type == T_FuncExpr) { + FuncExpr* func_expr = (FuncExpr*)expr; + if (func_expr->funcid == 481) { // cast to int8 + Expr* sub_expr = (Expr*)linitial(func_expr->args); + if (sub_expr->type == T_Const) { + MOT_LOG_TRACE("evalConstExpr(): Found const expression within cast to int8 function expression"); + const_expr = (Const*)sub_expr; + } + } + } + + if (const_expr != nullptr) { + // extract integer value + result = const_expr->constvalue; + MOT_LOG_TRACE("evalConstExpr(): Expression evaluated to constant value %d", result); + } else { + MOT_LOG_TRACE("evalConstExpr(): Could not infer const expression"); + } + + return result; +} + +bool getLimitCount(Query* query, int* limit_count) +{ + bool result = false; + if (query->limitOffset) { + MOT_LOG_TRACE("getLimitCount(): invalid limit clause - encountered limitOffset clause"); + } else if (query->limitCount) { + *limit_count = evalConstExpr((Expr*)query->limitCount); + if (*limit_count == 0) { + MOT_LOG_TRACE("getLimitCount(): limit clause has limit zero - disqualifying"); + } else { + result = true; + } + } else { + *limit_count = 0; // no limit clause + result = true; + } + return result; +} + +static inline bool isValidAggregateResultType(int restype) +{ + bool result = false; + if ((restype == INT1OID) || (restype == INT2OID) || (restype == INT4OID) || (restype == INT8OID) || + (restype == FLOAT4OID) || (restype == FLOAT8OID) || (restype == NUMERICOID)) { + result = true; + } + return result; +} + +static bool isAvgAggregateOperator(int funcid) +{ + bool result = false; + if ((funcid == INT8AVGFUNCOID) || (funcid == INT4AVGFUNCOID) || (funcid == INT2AVGFUNCOID) || + (funcid == INT1AVGFUNCOID) || (funcid == FLOAT4AVGFUNCOID) || (funcid == FLOAT8AVGFUNCOID) || + (funcid == NUMERICAVGFUNCOID)) { + result = true; + } + return result; +} + +static bool isSumAggregateOperator(int funcid) +{ + bool result = false; + if ((funcid == INT8SUMFUNCOID) /* int8_sum */ || (funcid == INT4SUMFUNCOID) /* int4_sum */ || + (funcid == INT2SUMFUNCOID) /* int2_sum */ || (funcid == 2110) /* float4pl */ || + (funcid == 2111) /* float8pl */ || (funcid == NUMERICSUMFUNCOID) /* numeric_sum */) { + result = true; + } + return result; +} + +static bool isMaxAggregateOperator(int funcid) +{ + bool result = false; + if ((funcid == INT8LARGERFUNCOID) /* int8larger */ || (funcid == INT4LARGERFUNCOID) /* int4larger */ || + (funcid == INT2LARGERFUNCOID) /* int2larger */ || (funcid == 5538) /* int1larger */ || + (funcid == 2119) /* float4larger */ || (funcid == 2120) /* float8larger */ || + (funcid == NUMERICLARGERFUNCOID) /* numeric_larger */ || (funcid == 2126) /* timestamp_larger */ || + (funcid == 2122) /* date_larger */ || (funcid == 2244) /* bpchar_larger */ || + (funcid == 2129) /* text_larger */) { + result = true; + } + return result; +} + +static bool isMinAggregateOperator(int funcid) +{ + bool result = false; + if ((funcid == INT8SMALLERFUNCOID) /* int8smaller */ || (funcid == INT4SMALLERFUNCOID) /* int4smaller */ || + (funcid == INT2SMALLERFUNCOID) /* int2smaller */ || // not int1 function was found for MIN operator + (funcid == 2135) /* float4smaller */ || (funcid == 2136) /* float8smaller */ || + (funcid == NUMERICSMALLERFUNCOID) /* numeric_smaller */ || (funcid == 2142) /* timestamp_smaller */ || + (funcid == 2138) /* date_smaller */ || (funcid == 2245) /* bpchar_smaller */ || + (funcid == 2145) /* text_smaller */) { + result = true; + } + return result; +} + +static bool isCountAggregateOperator(int funcid) +{ + bool result = false; + if ((funcid == 2147) /* int8inc_any */ || (funcid == 2803) /* int8inc */) { + result = true; + } + return result; +} + +static JitAggregateOperator classifyAggregateOperator(int funcid) +{ + JitAggregateOperator result = JIT_AGGREGATE_NONE; + if (isAvgAggregateOperator(funcid)) { + result = JIT_AGGREGATE_AVG; + } else if (isSumAggregateOperator(funcid)) { + result = JIT_AGGREGATE_SUM; + } else if (isMaxAggregateOperator(funcid)) { + result = JIT_AGGREGATE_MAX; + } else if (isMinAggregateOperator(funcid)) { + result = JIT_AGGREGATE_MIN; + } else if (isCountAggregateOperator(funcid)) { + result = JIT_AGGREGATE_COUNT; + } + return result; +} + +static int classifyAggregateAvgType(int funcid, int* element_count) +{ + int element_type = -1; + + switch (funcid) { + case INT8AVGFUNCOID: + case NUMERICAVGFUNCOID: + // the current_aggregate is a 2 numeric array + element_type = NUMERICOID; + *element_count = 2; + break; + + case INT4AVGFUNCOID: + case INT2AVGFUNCOID: + case 5537: // int1 avg + // the current_aggregate is a 2 int8 array + element_type = INT8OID; + *element_count = 2; + break; + + case 2104: // float4 + case 2105: // float8 + // the current_aggregate is a 3 float8 array + element_type = FLOAT8OID; + *element_count = 3; + break; + + default: + MOT_LOG_TRACE("Unsupported aggregate AVG() operator function type: %d", funcid); + break; + } + + return element_type; +} + +static inline bool isValidAggregateFunction(int funcid) +{ + bool result = false; + // check for aggregate_dummy leading to numeric_add and others (see src/include/catalog/pg_proc.h and + // pg_aggregate.h) + if (classifyAggregateOperator(funcid) != JIT_AGGREGATE_NONE) { + result = true; + } + return result; +} + +static inline bool isValidAggregateDistinctClause(const List* agg_distinct) +{ + bool result = false; + + if (agg_distinct == nullptr) { + result = true; + } else if (list_length(agg_distinct) != 1) { + MOT_LOG_TRACE("Unsupported DISTINCIT specifier with more than one sort clause"); + } else { + SortGroupClause* sgc = (SortGroupClause*)linitial(agg_distinct); + if (sgc->groupSet) { + MOT_LOG_TRACE("Unsupported DISTINCIT specifier with group-set flag"); + } else { + result = true; + } + } + + return result; +} + +static bool getTargetEntryAggCountStar(Aggref* agg_ref, JitAggregate* aggregate) +{ + JitAggregateOperator aggOp = classifyAggregateOperator(agg_ref->aggfnoid); + bool isCountStar = (agg_ref->aggstar && (aggOp == JIT_AGGREGATE_COUNT) && (agg_ref->aggtype == INT8OID)); + if (isCountStar) { + aggregate->_aggreaget_op = aggOp; + aggregate->_element_type = agg_ref->aggtype; + aggregate->_avg_element_type = -1; + aggregate->_func_id = agg_ref->aggfnoid; + aggregate->m_funcCollationId = agg_ref->inputcollid; + aggregate->_table = nullptr; + aggregate->_table_column_id = -1; + aggregate->_distinct = (agg_ref->aggdistinct != nullptr) ? true : false; + return true; + } else { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported empty aggregate argument list"); + return false; + } +} + +static bool getTargetEntryAggCountConst(TargetEntry* sub_te, Aggref* agg_ref, JitAggregate* aggregate) +{ + Const* constValue = (Const*)sub_te->expr; + JitAggregateOperator aggOp = classifyAggregateOperator(agg_ref->aggfnoid); + bool isCountConst = ((aggOp == JIT_AGGREGATE_COUNT) && (agg_ref->aggtype == INT8OID) && + (constValue->consttype == INT4OID) && (DatumGetInt32(constValue->constvalue) != 0)); + if (isCountConst) { + aggregate->_aggreaget_op = aggOp; + aggregate->_element_type = agg_ref->aggtype; + aggregate->_avg_element_type = -1; + aggregate->_func_id = agg_ref->aggfnoid; + aggregate->_table = nullptr; + aggregate->_table_column_id = -1; + aggregate->_distinct = (agg_ref->aggdistinct != nullptr) ? true : false; + return true; + } else { + MOT_LOG_TRACE( + "getTargetEntryAggregateOperator(): Unsupported aggregate argument list with length unequal to 1"); + return false; + } +} + +bool getTargetEntryAggregateOperator(Query* query, TargetEntry* target_entry, JitAggregate* aggregate) +{ + bool result = false; + + Aggref* agg_ref = (Aggref*)target_entry->expr; + int aggArgCount = list_length(agg_ref->args); + if (agg_ref->aggorder) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with ORDER BY specifiers"); + } else if (!isValidAggregateFunction(agg_ref->aggfnoid)) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator %d", agg_ref->aggfnoid); + } else if (!isValidAggregateResultType(agg_ref->aggtype)) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate result type %d", agg_ref->aggtype); + } else if (!isValidAggregateDistinctClause(agg_ref->aggdistinct)) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate distinct clause"); + } else if (aggArgCount == 0) { + // special case: count(*) + result = getTargetEntryAggCountStar(agg_ref, aggregate); + } else if (aggArgCount == 1) { + TargetEntry* sub_te = (TargetEntry*)linitial(agg_ref->args); + if (sub_te->expr->type == T_Const) { + // special case: count(1) + result = getTargetEntryAggCountConst(sub_te, agg_ref, aggregate); + } else if (sub_te->expr->type != T_Var) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with non-column argument"); + } else { + Var* var_expr = (Var*)sub_te->expr; + int result_type = var_expr->vartype; + if (!IsTypeSupported(result_type)) { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate operator with column type %d", + result_type); + } else { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): target entry for aggregate query is jittable"); + aggregate->_aggreaget_op = classifyAggregateOperator(agg_ref->aggfnoid); + aggregate->_element_type = result_type; + if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { + aggregate->_avg_element_type = + classifyAggregateAvgType(agg_ref->aggfnoid, &aggregate->_avg_element_count); + } else { + aggregate->_avg_element_type = -1; + } + aggregate->_func_id = agg_ref->aggfnoid; + aggregate->_table = getRealTable(query, var_expr->varno, var_expr->varattno); + aggregate->_table_column_id = + getRealColumnId(query, var_expr->varno, var_expr->varattno, aggregate->_table); + aggregate->_distinct = (agg_ref->aggdistinct != nullptr) ? true : false; + result = true; + } + } + } else { + MOT_LOG_TRACE("getTargetEntryAggregateOperator(): Unsupported aggregate list length %d", aggArgCount); + } + + return result; +} } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.h b/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.h index 91e9d9405..a4d7502a7 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_expr.h @@ -112,7 +112,14 @@ enum JitExprType { /** @var Sub-link expression type (for a sub-query). */ JIT_EXPR_TYPE_SUBLINK, - JIT_EXPR_TYPE_BOOL + /** @var Boolean expression type. */ + JIT_EXPR_TYPE_BOOL, + + /** @var Scalar Array operation expression type. */ + JIT_EXPR_TYPE_SCALAR_ARRAY_OP, + + /** @var Coerce via IO expression type. */ + JIT_EXPR_TYPE_COERCE_VIA_IO }; /** @enum Join scan types. */ @@ -133,6 +140,24 @@ enum JitJoinScanType { JIT_JOIN_SCAN_RANGE }; +/** @enum Join types. */ +enum class JitJoinType { + /** @var Inner join (includes records that have matching values in both tables). */ + JIT_JOIN_INNER, + + /** @var Left join (include all records from the left table, and the matched records from the right table). */ + JIT_JOIN_LEFT, + + /** @var full join (includes all records when there is a match in either left or right table). */ + JIT_JOIN_FULL, + + /** @var Right join (includes all records from the right table, and the matched records from the left table). */ + JIT_JOIN_RIGHT, + + /** @var Invalid join type. */ + JIT_JOIN_INVALID +}; + enum JoinClauseType { JoinClauseNone, JoinClauseExplicit, JoinClauseImplicit }; struct JitExpr { @@ -143,10 +168,10 @@ struct JitExpr { Expr* _source_expr; /** @var The expression result type. */ - int _result_type; + Oid _result_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; }; /** @struct A parsed constant expression. */ @@ -158,10 +183,10 @@ struct JitConstExpr { Expr* _source_expr; /** @var The constant type. */ - int _const_type; + Oid _const_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The constant value. */ Datum _value; @@ -172,17 +197,17 @@ struct JitConstExpr { /** @struct A parsed parameter expression. */ struct JitParamExpr { - /** @var The expression type. */ + /** @var The expression type (always @ref JIT_EXPR_TYPE_PARAM). */ JitExprType _expr_type; /** @var The original expression in the parsed query (required for convenient filter collection). */ Expr* _source_expr; - /** @var The parameter type (always @ref JIT_EXPR_TYPE_PARAM). */ - int _param_type; + /** @var The parameter type. */ + Oid _param_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The zero-based index of the parameter in the parameter array. */ int _param_id; @@ -197,10 +222,10 @@ struct JitVarExpr { Expr* _source_expr; /** @var The column type. */ - int _column_type; + Oid _column_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The column position. */ int _column_id; @@ -217,19 +242,22 @@ struct JitOpExpr { Expr* _source_expr; /** @var The expression result type. */ - int _result_type; + Oid _result_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The operator number. */ - int _op_no; + Oid _op_no; /** @var The correlating function id. */ - int _op_func_id; + Oid _op_func_id; - /** @var The operator arguments (3 at most). */ - JitExpr* _args[MOT_JIT_MAX_FUNC_EXPR_ARGS]; + /** @var Collation used by the operator. */ + Oid m_opCollationId; + + /** @var The operator arguments. */ + JitExpr** _args; /** @var The number of arguments used in the operator. */ int _arg_count; @@ -243,16 +271,19 @@ struct JitFuncExpr { Expr* _source_expr; /** @var The expression result type. */ - int _result_type; + Oid _result_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The correlating function id. */ - int _func_id; + Oid _func_id; - /** @var The function arguments (3 at most). */ - JitExpr* _args[MOT_JIT_MAX_FUNC_EXPR_ARGS]; + /** @var Collation used by the function. */ + Oid m_funcCollationId; + + /** @var The function arguments. */ + JitExpr** _args; /** @var The number of arguments used in the function. */ int _arg_count; @@ -268,9 +299,6 @@ struct JitSubLinkExpr { /** @var The sub-query result type. */ int _result_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; - /** @var The position of the sub-query plan in the sub-query plan array of the containing compound plan. */ int _sub_query_index; }; @@ -283,10 +311,10 @@ struct JitBoolExpr { Expr* _source_expr; /** @var The expression result type (always BOOLOID). */ - int _result_type; + Oid _result_type; - /** @var The position of the expression in the arg-is-null array. */ - int _arg_pos; + /** @var The expression evaluated collation. */ + Oid m_collationId; /** @var The correlating Boolean operator type. */ BoolExprType _bool_expr_type; @@ -298,6 +326,58 @@ struct JitBoolExpr { int _arg_count; }; +struct JitScalarArrayOpExpr { + /** @var The expression type (always @ref JIT_EXPR_TYPE_SCALAR_ARRAY_OP). */ + JitExprType _expr_type; + + /** @var The original expression in the parsed query. */ + Expr* _source_expr; + + /** @var The expression result type (always BOOLOID). */ + Oid _result_type; + + /** @var The expression evaluated collation. */ + Oid m_collationId; + + /** @var The operator number. */ + Oid _op_no; + + /** @var The correlating function id. */ + Oid _op_func_id; + + /** @var Collation used by the function. */ + Oid m_funcCollationId; + + /** @var Specifies the Boolean operation (OR or AND)*/ + bool m_useOr; + + /** @var The Scalar argument. */ + JitExpr* m_scalar; + + /** @var The array argument. */ + JitExpr** m_arrayElements; + + /** @var The array size. */ + int m_arraySize; +}; + +struct JitCoerceViaIOExpr { + /** @var The expression type (always @ref JIT_EXPR_TYPE_COERCE_VIA_IO). */ + JitExprType _expr_type; + + /** @var The original expression in the parsed query. */ + Expr* _source_expr; + + /** @var The expression result type (always BOOLOID). */ + Oid _result_type; + + /** @var The expression evaluated collation. */ + Oid m_collationId; + + /** @var The value to convert. */ + JitExpr* m_arg; +}; + /** @struct An expression tied to a table column. */ struct JitColumnExpr { /** @var The expression. */ @@ -314,6 +394,12 @@ struct JitColumnExpr { /** @var Specifies that this is a join expression (and the @ref _expr member is a Var expression). */ bool _join_expr; + + /** @var The position of the column according to the used index. */ + int _index_column_id; + + /** @var The operator class used with this column. */ + JitWhereOperatorClass _op_class; }; /** @struct An array of column-tied expressions. */ @@ -328,7 +414,7 @@ struct JitColumnExprArray { /** @struct A column select expression used in SELECT statements. */ struct JitSelectExpr { /** @var The expression. */ - JitVarExpr* _column_expr; + JitExpr* _expr; /** @var The zero-based output tuple column id. */ int _tuple_column_id; @@ -343,25 +429,10 @@ struct JitSelectExprArray { int _count; }; -/** @struct Scan filter. */ -struct JitFilter { - /** @var The left-hand side operand. */ - JitExpr* _lhs_operand; - - /** @var The right-hand side operand. */ - JitExpr* _rhs_operand; - - /** @var The operator to evaluate (see catalog/pg_operator.h). */ - int _filter_op; - - /** @var The function identifier of the operator to evaluate (see catalog/pg_proc.h). */ - int _filter_op_funcid; -}; - /** @struct Scan filter array. */ struct JitFilterArray { /** @var An array of scan filters used to filter rows in the scan. */ - JitFilter* _scan_filters; + JitExpr** _scan_filters; /** @var The number of filters used. */ int _filter_count; @@ -380,6 +451,9 @@ struct JitPointQuery { /** @var Any additional filters imposed on the scan. */ JitFilterArray _filters; + + /** @var Any additional one-time filters imposed on the scan. */ + JitFilterArray m_oneTimeFilters; }; /** @struct Index scan. */ @@ -387,8 +461,8 @@ struct JitIndexScan { /** @var The table being scanned. */ MOT::Table* _table; - /** @var The zero-based identifier of the index being used for scanning. */ - int _index_id; + /** @var The index being used for scanning. */ + MOT::Index* _index; /** @var The number of columns participating in the scan. */ int _column_count; @@ -426,6 +500,9 @@ struct JitIndexScan { /** @var Any additional filters imposed on the scan. */ JitFilterArray _filters; + + /** @var Additional one-time filters. */ + JitFilterArray m_oneTimeFilters; }; /** @struct Specifies aggregation parameters. */ @@ -436,6 +513,9 @@ struct JitAggregate { /** @var The aggregate function identifier. */ int _func_id; + /** @var Collation used by the function. */ + Oid m_funcCollationId; + /** @var The table column id to aggregate (we always aggregate into slot tuple column id 0). */ int _table_column_id; @@ -464,6 +544,9 @@ struct JitJoinExpr { int _inner_column_id; }; +/** @brief Queries whether the expression tree contains a column reference. */ +extern bool ExprHasVarRef(Expr* expr); + // Parent class for all expression visitors class ExpressionVisitor { public: @@ -473,7 +556,7 @@ public: virtual ~ExpressionVisitor() {} - virtual bool OnFilterExpr(int filterOp, int filterOpFuncId, Expr* lhs, Expr* rhs) + virtual bool OnFilterExpr(Expr* expr) { return true; } @@ -483,9 +566,6 @@ public: { return true; } - -protected: - DECLARE_CLASS_LOGGER() }; // Expression visitor that counts number of expressions @@ -550,6 +630,7 @@ public: if (_index_ops != nullptr) { // NOTE: this is a good use case for txn-level allocation, right? (the transaction being PREPARE command) MOT::MemSessionFree(_index_ops); + _index_ops = nullptr; } _query = nullptr; _table = nullptr; @@ -586,39 +667,64 @@ private: int RemoveSingleDuplicate(); + static bool IsSameOp(JitWhereOperatorClass lhs, JitWhereOperatorClass rhs); + static int IntCmp(int lhs, int rhs); static bool IndexOpCmp(const IndexOpClass& lhs, const IndexOpClass& rhs); + struct ExprCmp { + RangeScanExpressionCollector& m_exprCollector; + explicit ExprCmp(RangeScanExpressionCollector& exprCollector) : m_exprCollector(exprCollector) + {} + inline bool operator()(const JitColumnExpr& lhs, const JitColumnExpr& rhs) + { + return m_exprCollector.ExprCmpImp(lhs, rhs); + } + }; + + bool ExprCmpImp(const JitColumnExpr& lhs, const JitColumnExpr& rhs); + bool ScanHasHoles(JitIndexScanType scan_type) const; }; // Expression visitor that counts number of filters class FilterCounter : public ExpressionVisitor { public: - explicit FilterCounter(int* count) : _count(count) + explicit FilterCounter(int* count, int* oneTimeCount) : _count(count), m_oneTimeCount(oneTimeCount) {} ~FilterCounter() final { _count = nullptr; + m_oneTimeCount = nullptr; } - bool OnFilterExpr(int filterOp, int filterOpFuncId, Expr* lhs, Expr* rhs) final + bool OnFilterExpr(Expr* expr) final { - ++(*_count); + if (!ExprHasVarRef(expr)) { + ++(*m_oneTimeCount); + } else { + ++(*_count); + } return true; } private: int* _count; + int* m_oneTimeCount; }; // Expression visitor that collects filters class FilterCollector : public ExpressionVisitor { public: - FilterCollector(Query* query, JitFilterArray* filter_array, int* count) - : _query(query), _filter_array(filter_array), _filter_count(count) + FilterCollector(Query* query, JitFilterArray* filter_array, int* count, JitFilterArray* oneTimeFilterArray, + int* oneTimeFilterCount) + : _query(query), + _filter_array(filter_array), + _filter_count(count), + m_oneTimeFilterArray(oneTimeFilterArray), + m_oneTimeFilterCount(oneTimeFilterCount) {} ~FilterCollector() final @@ -626,14 +732,18 @@ public: _query = nullptr; _filter_array = nullptr; _filter_count = nullptr; + m_oneTimeFilterArray = nullptr; + m_oneTimeFilterCount = nullptr; } - bool OnFilterExpr(int filterOp, int filterOpFuncId, Expr* lhs, Expr* rhs) final; + bool OnFilterExpr(Expr* expr) final; private: Query* _query; JitFilterArray* _filter_array; int* _filter_count; + JitFilterArray* m_oneTimeFilterArray; + int* m_oneTimeFilterCount; void Cleanup(); }; @@ -645,7 +755,9 @@ public: {} ~SubLinkFetcher() final - {} + { + _subLink = nullptr; + } inline SubLink* GetSubLink() { @@ -660,6 +772,7 @@ private: int _count; }; +JitExpr* parseExpr(Query* query, Expr* expr, int depth, bool* hasVarExpr = nullptr); MOT::Table* getRealTable(const Query* query, int table_ref_id, int column_id); int getRealColumnId(const Query* query, int table_ref_id, int column_id, const MOT::Table* table); void freeExpr(JitExpr* expr); @@ -667,10 +780,13 @@ bool visitSearchExpressions(Query* query, MOT::Table* table, MOT::Index* index, ExpressionVisitor* visitor, bool include_join_exprs, JitColumnExprArray* pkey_exprs = nullptr); bool getSearchExpressions(Query* query, MOT::Table* table, MOT::Index* index, bool include_pkey, JitColumnExprArray* search_exprs, int* count, bool use_join_clause); +Node* getJoinQualifiers(const Query* query); bool getRangeSearchExpressions( Query* query, MOT::Table* table, MOT::Index* index, JitIndexScan* index_scan, JoinClauseType join_clause_type); bool getTargetExpressions(Query* query, JitColumnExprArray* target_exprs); bool getSelectExpressions(Query* query, JitSelectExprArray* select_exprs); +bool getLimitCount(Query* query, int* limit_count); +bool getTargetEntryAggregateOperator(Query* query, TargetEntry* target_entry, JitAggregate* aggregate); } // namespace JitExec #endif /* JIT_PLAN_EXPR_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.cpp b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.cpp new file mode 100644 index 000000000..0990a8491 --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.cpp @@ -0,0 +1,1313 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_plan_sp.cpp + * JIT execution plan generation for stored procedures. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_plan_sp.cpp + * + * ------------------------------------------------------------------------- + */ + +/* + * ATTENTION: Be sure to include global.h before postgres.h to avoid conflict between libintl.h (included in global.h) + * and c.h (included in postgres.h). + */ +#include "global.h" +#include "jit_plan.h" +#include "jit_plan_expr.h" +#include "jit_common.h" +#include "jit_source_map.h" +#include "mm_session_api.h" +#include "mm_global_api.h" +#include "utilities.h" +#include "jit_profiler.h" + +#include "nodes/pg_list.h" +#include "catalog/pg_aggregate.h" +#include "storage/mot/jit_exec.h" + +#include "utils/plpgsql.h" +#include "catalog/pg_language.h" +#include "parser/analyze.h" +#include "executor/executor.h" +#include "catalog/pg_proc.h" +#include "utils/builtins.h" + +#include "jit_plan_sp.h" + +#include + +namespace JitExec { +DECLARE_LOGGER(JitPlanSp, JitExec) + +static bool NameMatchesRegExList(const std::string& name, const std::string& regExList) +{ + std::string::size_type pos = 0; + std::string::size_type nextPos = regExList.find(';'); + + MOT_LOG_TRACE("Matching name %s against reg-ex list: %s", name.c_str(), regExList.c_str()); + while (pos < regExList.length()) { + std::string regExStr = + (nextPos != std::string::npos) ? regExList.substr(pos, nextPos - pos) : regExList.substr(pos); + std::regex regEx(regExStr); + if (std::regex_match(name, regEx)) { + MOT_LOG_TRACE("Name %s matches regular expression %s", name.c_str(), regExStr.c_str()); + return true; + } + if (nextPos == std::string::npos) { + break; + } + pos = nextPos + 1; + nextPos = regExList.find(';', pos); + } + MOT_LOG_TRACE("Name %s does not match regular expression list %s", name.c_str(), regExList.c_str()); + return false; +} + +static bool IsFunctionAllowed(const std::string& functionName) +{ + return NameMatchesRegExList(functionName, MOT::GetGlobalConfiguration().m_spCodegenAllowed); +} + +static bool IsFunctionProhibited(const std::string& functionName) +{ + return NameMatchesRegExList(functionName, MOT::GetGlobalConfiguration().m_spCodegenProhibited); +} + +static bool IsPureFunctionAllowed(const std::string& functionName) +{ + return NameMatchesRegExList(functionName, MOT::GetGlobalConfiguration().m_pureSPCodegen); +} + +static JitFunctionPlan* GetFunctionPlan(const char* functionName, Oid functionOid) +{ + // we embed function id in qualified function name to distinguish between overrides of the same function + MOT::mot_string qualifiedFunctionName; + if (!qualifiedFunctionName.format("%s.%u", functionName, (unsigned)functionOid)) { + MOT_LOG_TRACE("Failed to format qualified function name"); + return nullptr; + } + + // enforce white-list with regular expressions here for allowed function names + std::string fname(functionName); + if (!IsFunctionAllowed(fname)) { + MOT_LOG_TRACE("Invoked SP %s disqualified for JIT compilation by allowed white-list", functionName); + return nullptr; + } + + // enforce black list with regular expressions here for disallowed function names + if (IsFunctionProhibited(fname)) { + MOT_LOG_TRACE("Invoked SP %s disqualified for JIT compilation by prohibited black-list", functionName); + return nullptr; + } + + if (u_sess->mot_cxt.jit_compile_depth > MOT_JIT_MAX_COMPILE_DEPTH) { + MOT_LOG_TRACE("GetFunctionPlan(): Reached maximum compile depth"); + return nullptr; + } + + SPIAutoConnect spiAutoConn; + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while triggering function %s/%u compilation: %s (%u)", + functionName, + functionOid, + SPI_result_code_string(rc), + rc); + return nullptr; + } + + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile HeapTuple procTuple = nullptr; + volatile PLpgSQL_function* func = nullptr; + volatile JitPlan* plan = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + // take read lock first before we try to get compiled function, this avoids race between us and committer + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + if (!HeapTupleIsValid(procTuple)) { + MOT_LOG_TRACE("SP %s disqualified: Oid %u not found in pg_proc", functionName, functionOid); + } else { + // now we trigger compilation of the function at this point unconditionally + // attention: if compilation failed then ereport is thrown. + func = GetPGCompiledFunction(functionOid, functionName); + if (func == nullptr) { + MOT_LOG_TRACE("Invoked SP %s disqualified for JIT compilation: No compilation result for function %u", + functionName, + functionOid); + } else { + ++func->use_count; + MOT_LOG_TRACE("GetFunctionPlan(): Increased use count of function %p to %lu: %s", + func, + func->use_count, + functionName); + // now we try to prepare plan for the function + plan = IsJittableFunction((PLpgSQL_function*)func, procTuple, functionOid, true); + } + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while preparing function %s/%u JIT plan: %s", functionName, functionOid, edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while preparing function %s/%u JIT plan: %s", + functionName, + functionOid, + edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + // cleanup + if (procTuple != nullptr) { + ReleaseSysCache(procTuple); + } + + // function use count is kept positive until code-generation phase finishes + if ((func != nullptr) && (plan == nullptr)) { + --func->use_count; + MOT_LOG_TRACE( + "GetFunctionPlan(): Decreased use count of function %p to %lu: %s", func, func->use_count, functionName); + } + return (JitFunctionPlan*)plan; +} + +static List* GetFuncDefaultParams( + Oid functionOid, int passedArgCount, int** defaultParamPosArray, int* defaultParamCount) +{ + // code adapted from GetDefaultVale() called by func_get_detail() + HeapTuple tuple = nullptr; + Oid* argTypes = nullptr; + char** argNames = nullptr; + char* argModes = nullptr; + char* defaultsStr = nullptr; + List* defaults = nullptr; + int allArgCount = 0; + + do { // instead of goto + tuple = SearchSysCache1(PROCOID, functionOid); + if (!HeapTupleIsValid(tuple)) { + MOT_LOG_TRACE("Failed to find function by id: %u", functionOid); + break; + } + + // get defaults list + bool isNull = false; + Datum proArgDefaults = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proargdefaults, &isNull); + if (isNull) { + MOT_LOG_TRACE("Unexpected null attribute 'proargdefaults' in function %u", functionOid); + break; + } + defaultsStr = TextDatumGetCString(proArgDefaults); + MOT_LOG_TRACE("Seeing default parameter string: %s", defaultsStr); + defaults = (List*)stringToNode(defaultsStr); + if (!IsA(defaults, List)) { + MOT_LOG_TRACE("Unexpected non-list type for attribute 'proargdefaults' in function %u", functionOid); + break; + } + + // get defaults position list + allArgCount = get_func_arg_info(tuple, &argTypes, &argNames, &argModes); + Form_pg_proc procStruct = (Form_pg_proc)GETSTRUCT(tuple); + int argCount = procStruct->pronargs; + *defaultParamCount = procStruct->pronargdefaults; + + int2vector* defaultArgPos = nullptr; + if (argCount <= FUNC_MAX_ARGS_INROW) { + Datum defArgPosDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prodefaultargpos, &isNull); + if (isNull) { + MOT_LOG_TRACE("Unexpected null attribute 'prodefaultargpos' in function %u", functionOid); + break; + } + defaultArgPos = (int2vector*)DatumGetPointer(defArgPosDatum); + } else { + Datum defArgPosDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prodefaultargposext, &isNull); + if (isNull) { + MOT_LOG_TRACE("Unexpected null attribute 'prodefaultargposext' in function %u", functionOid); + break; + } + defaultArgPos = (int2vector*)PG_DETOAST_DATUM(defArgPosDatum); + } + + // do some sanity checks + if (passedArgCount > argCount) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, + "JIT Plan", + "Too many arguments (%d) passed to function %u accepting at most %u arguments", + passedArgCount, + functionOid, + argCount); + defaults = nullptr; + break; + } + if (allArgCount < argCount) { + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, + "JIT Plan", + "Inconsistent function %u state: all argument count %d is less than argument count %d", + functionOid, + allArgCount, + argCount); + defaults = nullptr; + break; + } + + // fetch the position array + FetchDefaultArgumentPos(defaultParamPosArray, defaultArgPos, argModes, allArgCount); + } while (0); + + pfree_ext(argTypes); + pfree_ext(argModes); + if (argNames != nullptr) { + for (int i = 0; i < allArgCount; i++) { + pfree_ext(argNames[i]); + } + pfree_ext(argNames); + } + pfree_ext(defaultsStr); + + if (tuple != nullptr) { + ReleaseSysCache(tuple); + } + return defaults; +} + +static Oid GetExprTypeAndCollation(Expr* expr, Oid* collation) +{ + switch (nodeTag(expr)) { + case T_Const: { + Const* constExpr = (Const*)expr; + *collation = constExpr->constcollid; + return constExpr->consttype; + } + + case T_Var: { + Var* varExpr = (Var*)expr; + *collation = varExpr->varcollid; + return varExpr->vartype; + } + + case T_Param: { + Param* paramExpr = (Param*)expr; + *collation = paramExpr->paramcollid; + return paramExpr->paramtype; + } + + case T_RelabelType: { + RelabelType* relabelType = (RelabelType*)expr; + *collation = relabelType->resultcollid; + return relabelType->resulttype; + } + + case T_OpExpr: { + OpExpr* opExpr = (OpExpr*)expr; + *collation = opExpr->opcollid; + return opExpr->opresulttype; + } + + case T_FuncExpr: { + FuncExpr* funcExpr = (FuncExpr*)expr; + *collation = funcExpr->funccollid; + return funcExpr->funcresulttype; + } + + case T_BoolExpr: { + *collation = InvalidOid; + return BOOLOID; + } + + case T_ScalarArrayOpExpr: { + ScalarArrayOpExpr* arrayExpr = (ScalarArrayOpExpr*)expr; + *collation = arrayExpr->inputcollid; + return BOOLOID; + } + + default: + break; + } + + *collation = InvalidOid; + return InvalidOid; +} + +static JitExpr* MakeNullExpr(Oid resultType, Oid collationId) +{ + uint32_t allocSize = sizeof(JitConstExpr); + JitConstExpr* jitExpr = (JitConstExpr*)MOT::MemSessionAlloc(allocSize); + if (jitExpr == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "JIT Plan", "Failed to allocate %u bytes for constant expression", allocSize); + return nullptr; + } + jitExpr->_expr_type = JIT_EXPR_TYPE_CONST; + jitExpr->_source_expr = nullptr; + jitExpr->_const_type = resultType; + jitExpr->m_collationId = collationId; + jitExpr->_value = PointerGetDatum(nullptr); + jitExpr->_is_null = true; + return (JitExpr*)jitExpr; +} + +static JitExpr* MakeNullExpr(Expr* expr) +{ + Oid collationId = InvalidOid; + Oid resultType = GetExprTypeAndCollation(expr, &collationId); + if (resultType == InvalidOid) { + MOT_LOG_TRACE("Failed to retrieve expression result type and collation"); + return nullptr; + } + return MakeNullExpr(resultType, collationId); +} + +extern FuncExpr* GetFuncExpr(Query* query) +{ + FuncExpr* funcExpr = nullptr; + int tableCount = list_length(query->rtable); + if (tableCount == 0) { + funcExpr = ValidateDirectInvokePlan(query); + } else if (tableCount == 1) { + funcExpr = ValidateSelectInvokePlan(query); + } + return funcExpr; +} + +extern JitPlan* JitPrepareInvokePlan(Query* query) +{ + if (!IsMotSPCodegenEnabled()) { + MOT_LOG_TRACE("Disqualifying invoke query - code generation for stored procedures is disabled"); + return nullptr; + } + + JitPlan* plan = nullptr; + MOT_LOG_TRACE("Preparing INVOKE plan"); + uint64_t startTime = GetSysClock(); + + if (query->commandType != CMD_SELECT) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying invoke query - not a SELECT command"); + return nullptr; + } + if (!CheckQueryAttributes(query, false, false, false)) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying invoke query - Invalid query attributes"); + return nullptr; + } + + // disallow invoke that returns a record (tuple with single composite attribute) + // this happens when the SP has more than one output parameters, and we invoke the SP like this: + // "select SP_NAME(...)" + // we support only this kind of invocation: + // "select * from SP_NAME(...)" + if (IsTargetListSingleRecordFunc(query->targetList)) { + if (u_sess->mot_cxt.jit_compile_depth == 0) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying query - Return type is record in top-level query"); + return nullptr; + } + } + + FuncExpr* funcExpr = GetFuncExpr(query); + if (funcExpr == nullptr) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying invoke query - Invalid invoke plan"); + return nullptr; + } + + HeapTuple procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcExpr->funcid)); + if (!HeapTupleIsValid(procTuple)) { + MOT_LOG_TRACE( + "JitPrepareInvokePlan(): Disqualifying invoke query - Failed to find function by id: %u", funcExpr->funcid); + return nullptr; + } + + // Validate the procedure first + Form_pg_proc procStruct = (Form_pg_proc)GETSTRUCT(procTuple); + char* funcName = pstrdup(NameStr(procStruct->proname)); + bool isNull = false; + Datum prokindDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_prokind, &isNull); + bool proIsProcedure = isNull ? false : PROC_IS_PRO(CharGetDatum(prokindDatum)); + Datum packageDatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_package, &isNull); + bool isPackage = isNull ? false : DatumGetBool(packageDatum); + TransactionId funcXmin = HeapTupleGetRawXmin(procTuple); + ReleaseSysCache(procTuple); + + if (!proIsProcedure) { + MOT_LOG_TRACE( + "JitPrepareInvokePlan(): Disqualifying invoke query - Skipping function %s as it not a SP", funcName); + pfree(funcName); + return nullptr; + } + + if (isPackage) { + MOT_LOG_TRACE( + "JitPrepareInvokePlan(): Disqualifying invoke query - Skipping SP %s as it is a package", funcName); + pfree(funcName); + return nullptr; + } + + JitFunctionPlan* funcPlan = nullptr; + JitExpr** args = nullptr; + JitExpr** defaultParams = nullptr; + char* funcNameCopy = nullptr; + int argNum = 0; + int usedDefaultParamCount = 0; + + do { // instead of goto + Oid functionId = funcExpr->funcid; + Oid* argTypes = nullptr; + int nargs = 0; + funcPlan = GetFunctionPlan(funcName, functionId); + if (funcPlan == nullptr) { + if (GetActiveNamespaceFunctionId() == InvalidOid) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying invoke query - Failed to prepare JIT plan for" + " function %s on top-level query", + funcName); + break; + } + MOT_LOG_TRACE("JitPrepareInvokePlan(): Preparing plan for unjittable invoked function %s by id %u", + funcName, + functionId); + (void)get_func_signature(functionId, &argTypes, &nargs); + } else { + MOT_LOG_TRACE("Using function %u plan with txn id %" PRIu64, functionId, funcPlan->m_function->fn_xmin); + } + + // add default parameters if needed + // NOTE: we do not support yet NamedArgExpr for CallableStatement or named-notation call style + int passedArgCount = list_length(funcExpr->args); + int requiredArgCount = funcPlan ? (int)funcPlan->m_argCount : nargs; + if (passedArgCount < requiredArgCount) { + // get the default parameter list with positions + MOT_LOG_TRACE("Preparing invoke query with default parameters"); + int* defaultParamPosArray = nullptr; + int defaultParamCount = 0; + List* defaultParamList = + GetFuncDefaultParams(functionId, passedArgCount, &defaultParamPosArray, &defaultParamCount); + if (defaultParamList == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_UNAVAILABLE, + "JIT Plan", + "Failed to get default parameters for function %u: %s", + functionId, + funcName); + break; + } + + // we do our best to skip any holes in the default parameter list, leaving unspecified parameters as nulls + // for now we do so only for positional call-style (i.e. parameters are NOT named), + // UNLIKE PG, we do not just append the required amount of defaults, but we actually append the defaults in + // their expected position + usedDefaultParamCount = requiredArgCount - passedArgCount; // including any holes + + // allocate default parameter array + size_t allocSize = sizeof(JitExpr*) * usedDefaultParamCount; + defaultParams = (JitExpr**)MOT::MemSessionAlloc(allocSize); + if (defaultParams == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Invoke Plan", + "Failed to allocate %u bytes for %d function default parameters", + (unsigned)allocSize, + usedDefaultParamCount); + break; + } + errno_t erc = memset_s(defaultParams, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + int i = 0; + ListCell* lc = nullptr; + int defaultParamIndex = 0; + bool failed = false; + foreach (lc, defaultParamList) { + // skip initial defaults already passed as arguments + Expr* subExpr = (Expr*)lfirst(lc); + int pos = defaultParamPosArray[i]; + if (pos < passedArgCount) { + MOT_LOG_TRACE("Skipping default parameter %d", i); + ++i; + continue; + } + // skip the hole + int defaultPos = passedArgCount + defaultParamIndex; + while (defaultPos < pos) { + MOT_LOG_TRACE("Skipping missing default parameter at position %d", defaultPos); + JitExpr* defaultParam = MakeNullExpr(subExpr); + if (defaultParam == nullptr) { + MOT_LOG_TRACE("Failed to parse default parameter expression"); + failed = true; + break; + } + defaultParams[defaultParamIndex++] = defaultParam; + defaultPos = passedArgCount + defaultParamIndex; + } + if (failed) { + break; + } + MOT_ASSERT(defaultPos == pos); + MOT_LOG_TRACE("Using default parameter %d in position %d", i, defaultPos); + JitExpr* defaultParam = parseExpr(query, subExpr, 0); + if (defaultParam == nullptr) { + MOT_LOG_TRACE("Failed to parse default parameter expression"); + failed = true; + break; + } + defaultParams[defaultParamIndex++] = defaultParam; + ++i; + } + if (failed) { + break; + } + // fill in any missing trailing default parameters + for (int j = defaultParamIndex; j < usedDefaultParamCount; ++j) { + Oid argType = (funcPlan != nullptr) ? funcPlan->m_paramTypes[j] : argTypes[j]; + JitExpr* defaultParam = MakeNullExpr(argType, InvalidOid); + if (defaultParam == nullptr) { + MOT_LOG_TRACE("Failed to prepare null default parameter"); + failed = true; + break; + } + defaultParams[j] = defaultParam; + } + } + + if (passedArgCount > 0) { + size_t allocSize = sizeof(JitExpr*) * passedArgCount; + args = (JitExpr**)MOT::MemSessionAlloc(allocSize); + if (args == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Invoke Plan", + "Failed to allocate %u bytes for %d function arguments", + (unsigned)allocSize, + passedArgCount); + break; + } + + ListCell* lc; + foreach (lc, funcExpr->args) { + Expr* subExpr = (Expr*)lfirst(lc); + // each parameter is a top-level expression at position 0 + JitExpr* arg = parseExpr(query, subExpr, 0); + if (arg == nullptr) { + MOT_LOG_TRACE("JitPrepareInvokePlan(): Disqualifying invoke query - Failed to process function " + "sub-expression %d", + argNum); + break; + } else { + args[argNum++] = arg; + } + } + + if (argNum < passedArgCount) { // failure + break; + } + } + + // allocate function name and copy it + size_t funcNameLen = strlen(funcName) + 1; + funcNameCopy = (char*)MOT::MemSessionAlloc(funcNameLen); + if (funcNameCopy == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Invoke Plan", + "Failed to allocate %u bytes for function name", + (unsigned)funcNameLen); + break; + } + errno_t erc = strcpy_s(funcNameCopy, funcNameLen, funcName); + securec_check(erc, "\0", "\0"); + + // allocate plan + size_t allocSize = sizeof(JitInvokePlan); + JitInvokePlan* invokePlan = (JitInvokePlan*)MOT::MemSessionAlloc(allocSize); + if (invokePlan == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Invoke Plan", + "Failed to allocate %u bytes for invoke plan", + (unsigned)allocSize); + break; + } + erc = memset_s(invokePlan, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + invokePlan->_plan_type = JIT_PLAN_INVOKE; + invokePlan->_command_type = JIT_COMMAND_INVOKE; + invokePlan->m_functionPlan = funcPlan; + invokePlan->_function_id = functionId; + invokePlan->_function_name = funcNameCopy; + invokePlan->m_functionTxnId = funcXmin; + invokePlan->_args = args; + invokePlan->_arg_count = passedArgCount; + invokePlan->m_defaultParams = defaultParams; + invokePlan->m_defaultParamCount = usedDefaultParamCount; + plan = (JitPlan*)invokePlan; + } while (0); + + if (plan == nullptr) { // cleanup on failure + if (funcNameCopy != nullptr) { + MOT::MemSessionFree(funcNameCopy); + } + if (args != nullptr) { + for (int i = 0; i < argNum; ++i) { + if (args[i] != nullptr) { + freeExpr(args[i]); + } + } + MOT::MemSessionFree(args); + } + if (defaultParams != nullptr) { + for (int i = 0; i < usedDefaultParamCount; ++i) { + if (defaultParams[i] != nullptr) { + freeExpr(defaultParams[i]); + } + } + MOT::MemSessionFree(defaultParams); + } + if (funcPlan != nullptr) { + JitDestroyPlan((JitPlan*)funcPlan); + } + } + pfree(funcName); + + if (plan != nullptr) { + uint64_t endTime = GetSysClock(); + uint64_t planTimeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + MOT_LOG_TRACE("INVOKE Plan time %" PRIu64 " micros", planTimeMicros); + } + return plan; +} + +static bool RegenerateCallParamInfo(JitCallSite* callSite, FuncParamInfoList* paramInfoList, + PLpgSQL_function* function, const ExprQueryAttrs& attrs, char* queryString, JitContextUsage usage) +{ + callSite->m_callParamCount = bms_num_members(attrs.m_paramNos); + MOT_LOG_TRACE("Creating call param info of size %d/%d for sub-query: %s", + callSite->m_callParamCount, + paramInfoList->m_numParams, + queryString); + if (callSite->m_callParamCount > 0) { + size_t allocSize = sizeof(JitCallParamInfo) * callSite->m_callParamCount; + callSite->m_callParamInfo = (JitCallParamInfo*)JitMemAlloc(allocSize, usage); + if (callSite->m_callParamInfo == nullptr) { + MOT_LOG_ERROR("Failed to allocate %u bytes for %d call parameters", allocSize, callSite->m_callParamCount); + return false; + } + errno_t erc = memset_s(callSite->m_callParamInfo, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } else { + callSite->m_callParamInfo = nullptr; + } + + int callParamIndex = 0; + if (attrs.m_paramNos && !bms_is_empty(attrs.m_paramNos)) { + int paramId = bms_next_member(attrs.m_paramNos, -1); + while (paramId >= 0) { + MOT_ASSERT(paramId < paramInfoList->m_numParams); + MOT_ASSERT(callParamIndex < callSite->m_callParamCount); + callSite->m_callParamInfo[callParamIndex].m_paramIndex = paramId; + callSite->m_callParamInfo[callParamIndex].m_paramType = paramInfoList->m_paramInfo[paramId].m_paramType; + int datumIndex = paramInfoList->m_paramInfo[paramId].m_datumId; + int argIndex = -1; + // for SP parameters we save parameter index, and for locals we save datum index + if (IsArgParam(function, datumIndex, &argIndex)) { + MOT_LOG_TRACE("Query param %d, mapped to datum %d, is argument with original index %d", + paramId, + datumIndex, + argIndex); + callSite->m_callParamInfo[callParamIndex].m_paramKind = JitCallParamKind::JIT_CALL_PARAM_ARG; + callSite->m_callParamInfo[callParamIndex].m_invokeArgIndex = argIndex; + callSite->m_callParamInfo[callParamIndex].m_invokeDatumIndex = -1; + } else { + MOT_LOG_TRACE("Query param %d, mapped to datum %d, is local var", paramId, datumIndex); + callSite->m_callParamInfo[callParamIndex].m_paramKind = JitCallParamKind::JIT_CALL_PARAM_LOCAL; + callSite->m_callParamInfo[callParamIndex].m_invokeArgIndex = -1; + callSite->m_callParamInfo[callParamIndex].m_invokeDatumIndex = datumIndex; + } + paramId = bms_next_member(attrs.m_paramNos, paramId); + ++callParamIndex; + } + } + + return true; +} + +static bool RegenerateSetNewQueryContext(JitCallSite* callSite, MotJitContext* jitContext, char* queryString, + JitContextUsage usage, const ExprQueryAttrs& attrs) +{ + // cleanup old attributes + if (callSite->m_queryContext != nullptr) { + DestroyJitContext(callSite->m_queryContext); + callSite->m_queryContext = nullptr; + } + if (callSite->m_queryString != nullptr) { + MOT_ASSERT(IsJitContextUsageGlobal(usage)); + JitMemFree(callSite->m_queryString, usage); + callSite->m_queryString = nullptr; + } + + // set new attributes + callSite->m_queryContext = jitContext; + if (jitContext != nullptr) { + callSite->m_queryString = nullptr; + callSite->m_queryContext->m_validState = JIT_CONTEXT_VALID; + } else { + callSite->m_queryString = DupString(queryString, usage); + if (callSite->m_queryString == nullptr) { + return false; + } + MOT_LOG_TRACE("RegenerateStmtQuery(): Cloned string %p on %s scope into call site %p: %s", + callSite->m_queryString, + JitContextUsageToString(usage), + callSite, + callSite->m_queryString); + } + // we keep this also for jittable sub-query as it might evolve into unjittable after revalidation + callSite->m_queryCmdType = attrs.m_query->commandType; + return true; +} + +static bool RegenerateTupleDesc( + JitCallSite* callSite, PLpgSQL_row* row, PLpgSQL_function* function, const ExprQueryAttrs& attrs) +{ + MemoryContext oldCtx = MemoryContextSwitchTo(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); + if (IsTargetListSingleRecordFunc(attrs.m_query->targetList) && IsCallSiteJittedFunction(callSite)) { + // this is a call into a jitted function returning record (without "* from"), so we build tuple desc according + // to the row datum receiving the result of this call query (so that composite tuple is not used in this case) + if (row != nullptr) { + callSite->m_tupDesc = PrepareTupleDescFromRow(row, function); + } else { + MOT_LOG_TRACE("RegenerateStmtQuery(): Disqualifying query - missing row when query returns record"); + return false; + } + } else { + callSite->m_tupDesc = ExecCleanTypeFromTL(attrs.m_query->targetList, false); + } + (void)MemoryContextSwitchTo(oldCtx); + if (callSite->m_tupDesc == nullptr) { + MOT_LOG_TRACE("Failed to create stored-procedure sub-query tuple descriptor from target list"); + return false; // safe cleanup during destroy + } + Assert(callSite->m_tupDesc->tdrefcount == -1); + return true; +} + +extern bool RegenerateStmtQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, FuncParamInfoList* paramInfoList, + PLpgSQL_function* function, JitCallSite* callSite, int queryCount, int subQueryIndex) +{ + // NOTE: This function is used for regenerating expired sub-query context. + char* queryString = expr->query; + MOT_LOG_TRACE("Regenerating sub-query plan: %s", queryString); + + // parse query and try to check if jittable and then create code for it + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(expr, function, &attrs)) { + MOT_LOG_TRACE("Failed to get query attributes from expression: %s", queryString); + return false; + } + + // we now check for a simple expression, which is OK + bool failed = false; + bool isSimpleExpr = IsSimpleExpr(attrs.m_spiPlan, attrs.m_query, &failed); + if (failed) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Failed to regenerate sub-query plan - cannot determine expression type"); + return false; + } + if (isSimpleExpr) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Found simple expression: %s", queryString); + // by no means should this happen during code regeneration, nevertheless for release build we declare failure + MOT_ASSERT(false); + return false; + } + + // at this point, if there are no vacant slots in the call site list, then we have a bug in query counting + if (subQueryIndex >= queryCount) { + MOT_ASSERT(false); // by ne means should this happen during code regeneration + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Missing vacant slot in call site list"); + return false; + } + + JitPlan* jitPlan = IsJittableQuery(attrs.m_query, queryString); + if (jitPlan == nullptr) { + // if this is an MOT query we still continue + StorageEngineType storageEngineType = SE_TYPE_UNSPECIFIED; + CheckTablesStorageEngine(attrs.m_query, &storageEngineType); + if (storageEngineType != SE_TYPE_MOT) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Sub-query is not jittable: %s", queryString); + return false; + } + } + + JitContextUsage usage = JitContextUsage::JIT_CONTEXT_LOCAL; + if (callSite->m_queryContext != nullptr) { + usage = callSite->m_queryContext->m_usage; + } + MotJitContext* jitContext = nullptr; + if (jitPlan != nullptr) { + jitContext = JitCodegenQuery(attrs.m_query, queryString, jitPlan, callSite->m_queryContext->m_usage); + if (jitContext == nullptr) { + CleanupExprQueryAttrs(&attrs); + JitDestroyPlan(jitPlan); + MOT_LOG_TRACE("Failed to generate code for sub-query: %s", queryString); + return false; + } + JitDestroyPlan(jitPlan); + jitPlan = nullptr; + + // get rid of parameter list (child signature may change...) + if (callSite->m_callParamInfo != nullptr) { + JitMemFree(callSite->m_callParamInfo, usage); + callSite->m_callParamInfo = nullptr; + } + + // cleanup tuple descriptor + if (callSite->m_tupDesc != nullptr) { + FreeTupleDesc(callSite->m_tupDesc); + callSite->m_tupDesc = nullptr; + } + + MOT_ASSERT(jitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY); + } + + if (!RegenerateCallParamInfo(callSite, paramInfoList, function, attrs, queryString, usage)) { + MOT_LOG_ERROR("Failed to create call parameters for sub-query: %s", queryString); + CleanupExprQueryAttrs(&attrs); + if (jitContext != nullptr) { + DestroyJitContext(jitContext); + } + return false; + } + + // cleanup old attributes and set new ones + if (!RegenerateSetNewQueryContext(callSite, jitContext, queryString, usage, attrs)) { + MOT_LOG_TRACE("Failed to set new sub-query context and query string"); + CleanupExprQueryAttrs(&attrs); + // Even though RegenerateSetNewQueryContext() can fail only when jitContext is nullptr, we still add here + // checking and destroy context for safety (if some code is changed in future). + if (jitContext != nullptr) { + DestroyJitContext(jitContext); + callSite->m_queryContext = nullptr; + } + return false; // safe cleanup during destroy + } + + // prepare a global tuple descriptor + if (!RegenerateTupleDesc(callSite, row, function, attrs)) { + MOT_LOG_TRACE("RegenerateStmtQuery(): Disqualifying query - failed to setup tuple descriptor"); + CleanupExprQueryAttrs(&attrs); + if (jitContext != nullptr) { + DestroyJitContext(jitContext); + callSite->m_queryContext = nullptr; + } + return false; // safe cleanup during destroy + } + + CleanupExprQueryAttrs(&attrs); + return true; +} + +static void DestroyCallSitePlanList(JitCallSitePlan* callSitePlanList, int queryCount) +{ + if (callSitePlanList == nullptr) { + return; + } + + for (int i = 0; i < queryCount; ++i) { + DestroyCallSitePlan(&callSitePlanList[i]); + } + MOT::MemSessionFree(callSitePlanList); +} + +inline bool CollectFunctionQueries(PLpgSQL_function* function, FuncParamInfoList* paramInfoList, + JitCallSitePlan* callSitePlanList, int queryCount, int* actualQueryCount) +{ + JitFunctionQueryCollector queryCollector(paramInfoList, function, callSitePlanList, queryCount); + bool result = VisitFunctionQueries(function, &queryCollector); + if (result) { + *actualQueryCount = queryCollector.GetActualQueryCount(); + } + return result; +} + +static JitCallSitePlan* PrepareFunctionQueries( + PLpgSQL_function* function, FuncParamInfoList* paramInfoList, int queryCount, int* actualQueryCount) +{ + MOT_LOG_TRACE("Preparing %d function queries", queryCount); + size_t allocSize = queryCount * sizeof(JitCallSitePlan); + JitCallSitePlan* callSitePlanList = (JitCallSitePlan*)MOT::MemSessionAlloc(allocSize); + if (callSitePlanList == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for function query plan list", + (unsigned)allocSize); + return nullptr; + } + + errno_t erc = memset_s(callSitePlanList, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // actual number of queries can be less than we expect, as there could be simple expressions, but we still expect + // at least one MOT jitted query + int preparedQueryCount = 0; + bool result = CollectFunctionQueries(function, paramInfoList, callSitePlanList, queryCount, &preparedQueryCount); + if (!result || (preparedQueryCount == 0)) { + if (!result) { + MOT_LOG_TRACE("Failed to prepare SP sub-queries (%d prepared successfully though)", preparedQueryCount); + } + DestroyCallSitePlanList(callSitePlanList, queryCount); + callSitePlanList = nullptr; + } + + // communicate back number of parsed queries only on success + if (result) { + *actualQueryCount = preparedQueryCount; + } + + return callSitePlanList; +} + +static bool CheckReturnType(PLpgSQL_function* function, const char* functionName) +{ + if (!IsTypeSupported(function->fn_rettype) && (function->fn_rettype != RECORDOID) && + (function->fn_rettype != VOIDOID)) { + MOT_LOG_TRACE("CheckReturnType(): Disqualifying function %s with unsupported return type %d", + functionName, + function->fn_rettype); + return false; + } + if (function->fn_retset) { + MOT_LOG_TRACE("CheckReturnType(): Disqualifying function %s with return set", functionName); + return false; + } + return true; +} + +static bool IsConstExpr(PLpgSQL_expr* expr, PLpgSQL_function* function, bool* failed) +{ + // parse query and try to check if it is a simple constant + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(expr, function, &attrs)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Prepare JIT Function Plan", "Failed to get query from expression"); + return false; + } + char* queryString = expr->query; + MOT_LOG_TRACE("Checking if expression query is constant: %s", queryString); + + // we now check for a simple expression, which is OK + bool isConst = false; + Oid constType = InvalidOid; + bool isSimple = IsSimpleExpr(attrs.m_spiPlan, attrs.m_query, failed, &isConst, &constType); + if (*failed) { + MOT_LOG_TRACE("Failed to determine expression type"); + return false; + } + + CleanupExprQueryAttrs(&attrs); + if (isSimple && isConst && IsTypeSupported(constType)) { + MOT_LOG_TRACE("Found simple constant expression: %s", queryString); + return true; + } + + return false; +} + +static bool FunctionStmtSupported(PLpgSQL_function* function) +{ + JitFunctionQueryVerifier queryVerifier; + return VisitFunctionQueries(function, &queryVerifier); +} + +static bool CheckParameters(PLpgSQL_function* function, const char* functionName) +{ + // check all datum types + for (int i = 0; i < function->ndatums; ++i) { + if (i == function->sql_bulk_exceptions_varno) { + // This datum is used only for SAVE EXCEPTIONS and SQL%BULK_EXCEPTION, which we don't support. + continue; + } + + PLpgSQL_datum* datum = function->datums[i]; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* var = (PLpgSQL_var*)datum; + if (!IsTypeSupported(var->datatype->typoid)) { + MOT_LOG_TRACE("CheckParameters(): Disqualifying function %s with unsupported datum %d (dno %d) type %d", + functionName, + i, + datum->dno, + var->datatype->typoid); + return false; + } + } + } + + // check argument default values are constants + for (int i = 0; i < function->fn_nargs; ++i) { + int varno = function->fn_argvarnos[i]; + PLpgSQL_datum* datum = function->datums[varno]; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* var = (PLpgSQL_var*)datum; + if (var->default_val != nullptr) { + bool failed = false; + bool isConst = IsConstExpr(var->default_val, function, &failed); + if (failed) { + MOT_LOG_TRACE("CheckParameters(): Disqualifying function %s - cannot determine constant expression" + " type", + functionName); + return false; + } + if (!isConst) { + MOT_LOG_TRACE("CheckParameters(): Disqualifying function %s with unsupported default value for " + "argument %d, datum %d (dno %d)", + functionName, + i, + varno, + datum->dno); + return false; + } + } + } + } + + return true; +} + +extern JitPlan* JitPrepareFunctionPlan( + PLpgSQL_function* function, const char* functionSource, const char* functionName, Oid functionOid, bool isStrict) +{ + MOT_LOG_TRACE("Preparing plan for function: %s with %d args", functionName, function->fn_nargs); + uint64_t startTime = GetSysClock(); + + // enforce white-list with regular expressions here for allowed function names + std::string fname(functionName); + if (!IsFunctionAllowed(fname)) { + MOT_LOG_TRACE("Function disqualified for JIT compilation by allowed white-list"); + return nullptr; + } + + // enforce black list with regular expressions here for disallowed function names + if (IsFunctionProhibited(fname)) { + MOT_LOG_TRACE("Function disqualified for JIT compilation by prohibited black-list"); + return nullptr; + } + + // verify function return type is supported + if (!CheckReturnType(function, functionName)) { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying function %s with unsupported return type", functionName); + return nullptr; + } + + // make sure we have a valid SPI connection + SPIAutoConnect spiAutoConn; + if (!spiAutoConn.IsConnected()) { + int rc = spiAutoConn.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while preparing plan for function %s: %s (%u)", + functionName, + SPI_result_code_string(rc), + rc); + return nullptr; + } + + // verify function variable types are supported + if (!CheckParameters(function, functionName)) { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying function %s with unsupported parameters types", functionName); + return nullptr; + } + + // verify function statements are jittable + if (!FunctionStmtSupported(function)) { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying function %s with unsupported statement", functionName); + return nullptr; + } + + // disqualify functions that do not execute sub-queries at all + int queryCount = CountFunctionQueries(function); + if (queryCount == 0) { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying function %s: no MOT sub-queries", functionName); + return nullptr; + } + + // prepare function parameters info for parsing sub-queries + FuncParamInfoList paramInfoList = {}; + if (!PrepareFuncParamInfoList(function, ¶mInfoList)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Prepare JIT Function Plan", + "Failed to prepare parameter information for stored procedure %s execution plan", + functionName); + return nullptr; + } + + // for profiler to work correctly, we need to reserve parent function id, and also setup name-space + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + MOT::mot_string qualifiedFunctionName; + if (!qualifiedFunctionName.format("%s.%u", functionName, (unsigned)functionOid)) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, + "Prepare JIT Function Plan", + "Failed to prepare qualified function name for stored procedure %s execution plan", + functionName); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + if (!PushJitSourceNamespace(functionOid, qualifiedFunctionName.c_str())) { + MOT_LOG_TRACE( + "Failed to push JIT source namespace for preparing stored procedure %s execution plan", functionName); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + // reserve slot for parent function id early enough, before child queries are processed + (void)JitProfiler::GetInstance()->GetProfileFunctionId(GetActiveNamespace(), functionOid); + } + + // verify all sub-queries are jittable + int actualQueryCount = -1; + JitCallSitePlan* callSitePlanList = PrepareFunctionQueries(function, ¶mInfoList, queryCount, &actualQueryCount); + + if (MOT::GetGlobalConfiguration().m_enableCodegenProfile) { + PopJitSourceNamespace(); + } + + bool failed = ((callSitePlanList == nullptr) && (actualQueryCount == -1)); + if (failed) { + MOT_LOG_TRACE("Failed to prepare sub-query plans for stored procedure %s execution plan", functionName); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + + // disqualify functions that execute queries on non-MOT tables, unless they are in the white list + bool unallowedPureFunction = ((actualQueryCount == 0) && !IsPureFunctionAllowed(functionName)); + if (unallowedPureFunction) { + MOT_LOG_TRACE("JitPreparePlan(): Disqualifying function %s: no MOT sub-queries actually found", functionName); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + + // save all JIT contexts of all sub-queries in a map inside the plan. + size_t allocSize = sizeof(JitFunctionPlan); + JitFunctionPlan* functionPlan = (JitFunctionPlan*)MOT::MemSessionAlloc(allocSize); + if (functionPlan == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for stored procedure %s execution plan", + (unsigned)allocSize, + functionName); + DestroyCallSitePlanList(callSitePlanList, queryCount); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + errno_t erc = memset_s(functionPlan, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // allocate function name and copy it + size_t funcNameLen = strlen(functionName) + 1; + functionPlan->_function_name = (char*)MOT::MemSessionAlloc(funcNameLen); + if (!functionPlan->_function_name) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for function name", + (unsigned)funcNameLen); + MOT::MemSessionFree(functionPlan); + DestroyCallSitePlanList(callSitePlanList, queryCount); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + erc = strcpy_s(functionPlan->_function_name, funcNameLen, functionName); + securec_check(erc, "\0", "\0"); + + functionPlan->_plan_type = JIT_PLAN_FUNCTION; + functionPlan->_command_type = JIT_COMMAND_FUNCTION; + functionPlan->_function_id = functionOid; + functionPlan->m_function = function; + allocSize = sizeof(Oid) * paramInfoList.m_numParams; + functionPlan->m_paramTypes = (Oid*)MOT::MemSessionAlloc(allocSize); + if (functionPlan->m_paramTypes == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for function parameter type array", + (unsigned)funcNameLen); + MOT::MemSessionFree(functionPlan->_function_name); + MOT::MemSessionFree(functionPlan); + DestroyCallSitePlanList(callSitePlanList, queryCount); + DestroyFuncParamInfoList(¶mInfoList); + return nullptr; + } + for (int i = 0; i < paramInfoList.m_numParams; ++i) { + functionPlan->m_paramTypes[i] = paramInfoList.m_paramInfo[i].m_paramType; + } + functionPlan->m_paramCount = (uint32_t)paramInfoList.m_numParams; + functionPlan->m_argCount = (uint32_t)function->fn_nargs; + functionPlan->_query_count = actualQueryCount; + functionPlan->m_callSitePlanList = callSitePlanList; + + DestroyFuncParamInfoList(¶mInfoList); + + uint64_t endTime = GetSysClock(); + uint64_t planTimeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + MOT_LOG_TRACE("Plan time %" PRIu64 " micros for SP: %s", planTimeMicros, functionPlan->_function_name); + return (JitPlan*)functionPlan; +} + +extern void JitDestroyFunctionPlan(JitFunctionPlan* plan) +{ + if (plan->m_function != nullptr) { + --plan->m_function->use_count; + MOT_LOG_TRACE("JitDestroyFunctionPlan(): Decreased use count of function %p to %lu: %s", + plan->m_function, + plan->m_function->use_count, + plan->_function_name); + plan->m_function = nullptr; + } + if (plan->_function_name != nullptr) { + MOT::MemSessionFree(plan->_function_name); + plan->_function_name = nullptr; + } + if (plan->m_paramTypes != nullptr) { + MOT::MemSessionFree(plan->m_paramTypes); + plan->m_paramTypes = nullptr; + } + if (plan->m_callSitePlanList != nullptr) { + DestroyCallSitePlanList(plan->m_callSitePlanList, plan->_query_count); + plan->m_callSitePlanList = nullptr; + } + MOT::MemSessionFree(plan); +} + +extern void JitDestroyInvokePlan(JitInvokePlan* plan) +{ + if (plan->_function_name != nullptr) { + MOT::MemSessionFree(plan->_function_name); + } + if (plan->_args != nullptr) { + for (int i = 0; i < plan->_arg_count; ++i) { + if (plan->_args[i] != nullptr) { + freeExpr(plan->_args[i]); + } + } + MOT::MemSessionFree(plan->_args); + } + if (plan->m_defaultParams != nullptr) { + for (int i = 0; i < plan->m_defaultParamCount; ++i) { + if (plan->m_defaultParams[i] != nullptr) { + freeExpr(plan->m_defaultParams[i]); + } + } + MOT::MemSessionFree(plan->m_defaultParams); + } + if (plan->m_functionPlan != nullptr) { + JitDestroyPlan((JitPlan*)plan->m_functionPlan); + } + MOT::MemSessionFree(plan); +} +} // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.h b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.h new file mode 100644 index 000000000..c35eadb5f --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_plan_sp.h + * JIT execution plan generation for stored procedures. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_plan_sp.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef JIT_PLAN_SP_H +#define JIT_PLAN_SP_H + +#include "jit_plan_sp_expr.h" + +namespace JitExec { +extern JitPlan* JitPrepareInvokePlan(Query* query); + +extern void JitDestroyFunctionPlan(JitFunctionPlan* plan); + +extern void JitDestroyInvokePlan(JitInvokePlan* plan); + +extern bool VisitFunctionQueries(PLpgSQL_function* function, JitFunctionQueryVisitor* visitor); + +extern bool RegenerateStmtQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, FuncParamInfoList* paramInfoList, + PLpgSQL_function* function, JitCallSite* callSite, int queryCount, int subQueryIndex); + +extern FuncExpr* GetFuncExpr(Query* query); +} // namespace JitExec + +#endif /* JIT_PLAN_SP_H */ \ No newline at end of file diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.cpp b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.cpp new file mode 100644 index 000000000..5821eada5 --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.cpp @@ -0,0 +1,1278 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_plan_sp_expr.cpp + * JIT SP execution plan expression helpers. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.cpp + * + * ------------------------------------------------------------------------- + */ + +/* + * ATTENTION: Be sure to include global.h before postgres.h to avoid conflict between libintl.h (included in global.h) + * and c.h (included in postgres.h). + */ +#include "global.h" +#include "jit_plan_sp_expr.h" +#include "jit_common.h" +#include "jit_source_map.h" +#include "storage/mot/jit_exec.h" +#include "parser/analyze.h" +#include "catalog/pg_proc.h" + +namespace JitExec { +DECLARE_LOGGER(JitPlanSpExpr, JitExec) + +bool PrepareFuncParamInfoList(PLpgSQL_function* function, FuncParamInfoList* paramInfoList) +{ + MOT_LOG_TRACE("Preparing function parameter info list for function: %s", function->fn_signature); + + // count var datums + MOT_LOG_TRACE("Seeing %d parameters", function->ndatums); + paramInfoList->m_numParams = function->ndatums; + paramInfoList->m_paramInfo = nullptr; + + // special case: function without parameters + if (paramInfoList->m_numParams == 0) { + return true; + } + + size_t allocSize = sizeof(FuncParamInfo) * paramInfoList->m_numParams; + paramInfoList->m_paramInfo = (FuncParamInfo*)MOT::MemSessionAlloc(allocSize); + if (paramInfoList->m_paramInfo == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for function parameter information Oid array", + (unsigned)allocSize); + return false; + } + errno_t erc = memset_s(paramInfoList->m_paramInfo, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + for (int i = 0; i < function->ndatums; ++i) { + PLpgSQL_datum* datum = function->datums[i]; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* varDatum = (PLpgSQL_var*)datum; + MOT_LOG_TRACE("Collecting var parameter with datum id %d name %s and type %d", + i, + varDatum->refname, + varDatum->datatype->typoid); + paramInfoList->m_paramInfo[i].m_datumId = i; + paramInfoList->m_paramInfo[i].m_paramType = varDatum->datatype->typoid; + allocSize = strlen(varDatum->datatype->typname) + 1; + paramInfoList->m_paramInfo[i].m_paramName = (char*)MOT::MemSessionAlloc(allocSize); + if (paramInfoList->m_paramInfo[i].m_paramName == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Prepare JIT Function Plan", + "Failed to allocate %u bytes for function parameter information type name", + (unsigned)allocSize); + for (int j = 0; j < i; ++j) { + MOT::MemSessionFree(paramInfoList->m_paramInfo[j].m_paramName); + } + MOT::MemSessionFree(paramInfoList->m_paramInfo); + return false; + } + erc = strcpy_s(paramInfoList->m_paramInfo[i].m_paramName, allocSize, varDatum->datatype->typname); + securec_check(erc, "\0", "\0"); + } else { + MOT_LOG_TRACE("Collecting other parameter with datum id %d", i); + paramInfoList->m_paramInfo[i].m_datumId = i; + paramInfoList->m_paramInfo[i].m_paramType = InvalidOid; + } + } + return true; +} + +void DestroyFuncParamInfoList(FuncParamInfoList* paramInfoList) +{ + for (int i = 0; i < paramInfoList->m_numParams; ++i) { + MOT::MemSessionFree(paramInfoList->m_paramInfo[i].m_paramName); + } + MOT::MemSessionFree(paramInfoList->m_paramInfo); +} + +static JitVisitResult VisitStmtListQueries(List* body, JitFunctionQueryVisitor* visitor, int* index); +static JitVisitResult VisitStmtBlockQueries( + PLpgSQL_stmt_block* stmtBlock, JitFunctionQueryVisitor* visitor, int* index); + +#define PROCESS_VISIT_RESULT(message, lineNo) \ + do { \ + if (result == JitVisitResult::JIT_VISIT_ERROR) { \ + visitor->OnError(message, lineNo); \ + } \ + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { \ + return result; \ + } \ + } while (0) + +static JitVisitResult VisitIfStmtQueries(PLpgSQL_stmt_if* ifStmt, JitFunctionQueryVisitor* visitor, int* index) +{ + JitVisitResult result = visitor->OnQuery(ifStmt->cond, nullptr, *index, false); + PROCESS_VISIT_RESULT("IF statement condition", ifStmt->lineno); + ++(*index); + + result = VisitStmtListQueries(ifStmt->then_body, visitor, index); + PROCESS_VISIT_RESULT("IF statement THEN body", ifStmt->lineno); + + result = VisitStmtListQueries(ifStmt->else_body, visitor, index); + PROCESS_VISIT_RESULT("IF statement ELSE body", ifStmt->lineno); + + ListCell* lc = nullptr; + foreach (lc, ifStmt->elsif_list) { + PLpgSQL_if_elsif* elseifStmt = (PLpgSQL_if_elsif*)lfirst(lc); + result = VisitStmtListQueries(elseifStmt->stmts, visitor, index); + PROCESS_VISIT_RESULT("IF statement ELSEIF body", ifStmt->lineno); + } + return result; +} + +static JitVisitResult VisitCaseStmtQueries(PLpgSQL_stmt_case* caseStmt, JitFunctionQueryVisitor* visitor, int* index) +{ + JitVisitResult result = JitVisitResult::JIT_VISIT_CONTINUE; + if (caseStmt->t_expr != nullptr) { + result = visitor->OnQuery(caseStmt->t_expr, nullptr, *index, false); + PROCESS_VISIT_RESULT("CASE statement expression", caseStmt->lineno); + ++(*index); + } + ListCell* lc = nullptr; + foreach (lc, caseStmt->case_when_list) { + PLpgSQL_case_when* caseWhenStmt = (PLpgSQL_case_when*)lfirst(lc); + result = VisitStmtListQueries(caseWhenStmt->stmts, visitor, index); + PROCESS_VISIT_RESULT("CASE WHEN body", caseWhenStmt->lineno); + } + + if (caseStmt->have_else && (caseStmt->else_stmts != nullptr)) { + result = VisitStmtListQueries(caseStmt->else_stmts, visitor, index); + PROCESS_VISIT_RESULT("CASE ELSE body", caseStmt->lineno); + } + return result; +} + +static JitVisitResult VisitSqlStmtQuery(PLpgSQL_stmt_execsql* execSqlStmt, JitFunctionQueryVisitor* visitor, int* index) +{ + PLpgSQL_row* row = nullptr; + if (execSqlStmt->into) { + if (execSqlStmt->row == nullptr) { + MOT_LOG_TRACE("Unsupported INTO record in EXECSQL statement: %s", execSqlStmt->sqlstmt->query); + return JitVisitResult::JIT_VISIT_ERROR; + } + row = execSqlStmt->row; + } + JitVisitResult result = visitor->OnQuery(execSqlStmt->sqlstmt, row, *index, execSqlStmt->into); + PROCESS_VISIT_RESULT("EXECSQL query", execSqlStmt->lineno); + ++(*index); + return result; +} + +static JitVisitResult VisitWhileStmtQueries(PLpgSQL_stmt_while* whileStmt, JitFunctionQueryVisitor* visitor, int* index) +{ + JitVisitResult result = visitor->OnQuery(whileStmt->cond, nullptr, *index, false); + PROCESS_VISIT_RESULT("WHILE statement condition", whileStmt->lineno); + ++(*index); + + result = VisitStmtListQueries(whileStmt->body, visitor, index); + PROCESS_VISIT_RESULT("WHILE statement body", whileStmt->lineno); + return result; +} + +static JitVisitResult VisitForiStmtQueries(PLpgSQL_stmt_fori* foriStmt, JitFunctionQueryVisitor* visitor, int* index) +{ + if (foriStmt->save_exceptions) { + MOT_LOG_TRACE("Unsupported SAVE EXCEPTIONS clause in FOR-I statement"); + visitor->OnError("FOR-I statement message", foriStmt->lineno); + return JitVisitResult::JIT_VISIT_ERROR; + } + + JitVisitResult result = JitVisitResult::JIT_VISIT_CONTINUE; + if (foriStmt->lower != nullptr) { + result = visitor->OnQuery(foriStmt->lower, nullptr, *index, false); + PROCESS_VISIT_RESULT("FOR statement lower bound expression", foriStmt->lineno); + ++(*index); + } + if (foriStmt->upper != nullptr) { + result = visitor->OnQuery(foriStmt->upper, nullptr, *index, false); + PROCESS_VISIT_RESULT("FOR statement upper bound expression", foriStmt->lineno); + ++(*index); + } + if (foriStmt->step != nullptr) { + result = visitor->OnQuery(foriStmt->step, nullptr, *index, false); + PROCESS_VISIT_RESULT("FOR statement step expression", foriStmt->lineno); + ++(*index); + } + + result = VisitStmtListQueries(foriStmt->body, visitor, index); + PROCESS_VISIT_RESULT("FOR statement body", foriStmt->lineno); + return result; +} + +static JitVisitResult VisitRaiseStmt(PLpgSQL_stmt_raise* stmtRaise, JitFunctionQueryVisitor* visitor, int* index) +{ + // no query involved, just checking it is supported + if ((strcmp(stmtRaise->message, MOT_JIT_PROFILE_BEGIN_MESSAGE) != 0) && + (strcmp(stmtRaise->message, MOT_JIT_PROFILE_END_MESSAGE) != 0)) { + visitor->OnError("RAISE statement message", stmtRaise->lineno); + return JitVisitResult::JIT_VISIT_ERROR; + } + ListCell* lc = nullptr; + foreach (lc, stmtRaise->options) { + PLpgSQL_raise_option* option = (PLpgSQL_raise_option*)lfirst(lc); + if ((option->opt_type != PLPGSQL_RAISEOPTION_HINT) || (option->expr->query == nullptr)) { + visitor->OnError("RAISE statement option", stmtRaise->lineno); + return JitVisitResult::JIT_VISIT_ERROR; + } + MOT_LOG_TRACE("Seeing hint text: %s", option->expr->query); + } + return JitVisitResult::JIT_VISIT_CONTINUE; +} + +static JitVisitResult VisitStmtListQueries(List* body, JitFunctionQueryVisitor* visitor, int* index) +{ + JitVisitResult result = JitVisitResult::JIT_VISIT_CONTINUE; + ListCell* lc = nullptr; + foreach (lc, body) { + PLpgSQL_stmt* stmt = (PLpgSQL_stmt*)lfirst(lc); + switch (stmt->cmd_type) { + case PLPGSQL_STMT_EXECSQL: + result = VisitSqlStmtQuery((PLpgSQL_stmt_execsql*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_PERFORM: + result = visitor->OnQuery(((PLpgSQL_stmt_perform*)stmt)->expr, nullptr, *index, false); + PROCESS_VISIT_RESULT("PERFORM statement", stmt->lineno); + ++(*index); + break; + + case PLPGSQL_STMT_BLOCK: + result = VisitStmtBlockQueries((PLpgSQL_stmt_block*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_IF: + result = VisitIfStmtQueries((PLpgSQL_stmt_if*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_CASE: + result = VisitCaseStmtQueries((PLpgSQL_stmt_case*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_LOOP: + result = VisitStmtListQueries(((PLpgSQL_stmt_loop*)stmt)->body, visitor, index); + PROCESS_VISIT_RESULT("LOOP statement body", stmt->lineno); + break; + + case PLPGSQL_STMT_WHILE: + result = VisitWhileStmtQueries((PLpgSQL_stmt_while*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_FORI: + result = VisitForiStmtQueries((PLpgSQL_stmt_fori*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_ASSIGN: + result = visitor->OnQuery(((PLpgSQL_stmt_assign*)stmt)->expr, nullptr, *index, false); + PROCESS_VISIT_RESULT("ASSIGN statement expression", stmt->lineno); + ++(*index); + break; + + case PLPGSQL_STMT_RETURN: + if (((PLpgSQL_stmt_return*)stmt)->expr != nullptr) { + result = visitor->OnQuery(((PLpgSQL_stmt_return*)stmt)->expr, nullptr, *index, false); + PROCESS_VISIT_RESULT("RETURN statement expression", stmt->lineno); + ++(*index); + } + break; + + case PLPGSQL_STMT_EXIT: + if (((PLpgSQL_stmt_exit*)stmt)->cond != nullptr) { + result = visitor->OnQuery(((PLpgSQL_stmt_exit*)stmt)->cond, nullptr, *index, false); + PROCESS_VISIT_RESULT("EXIT statement expression", stmt->lineno); + ++(*index); + } + break; + + case PLPGSQL_STMT_RAISE: + result = VisitRaiseStmt((PLpgSQL_stmt_raise*)stmt, visitor, index); + if (result != JitVisitResult::JIT_VISIT_CONTINUE) { + return result; + } + break; + + case PLPGSQL_STMT_NULL: + return JitVisitResult::JIT_VISIT_CONTINUE; + + case PLPGSQL_STMT_GOTO: + MOT_LOG_TRACE("Unsupported GOTO statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_FORS: + MOT_LOG_TRACE("Unsupported FORS statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_FORC: + MOT_LOG_TRACE("Unsupported FORC statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_FOREACH_A: + MOT_LOG_TRACE("Unsupported FOREACH statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_RETURN_NEXT: + MOT_LOG_TRACE("Unsupported GOTO statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_RETURN_QUERY: + MOT_LOG_TRACE("Unsupported RETURN-NEXT statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_DYNEXECUTE: + MOT_LOG_TRACE("Unsupported DYNEXECUTE statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_DYNFORS: + MOT_LOG_TRACE("Unsupported DYNCFORS statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_GETDIAG: + MOT_LOG_TRACE("Unsupported GETDIAG statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_OPEN: + MOT_LOG_TRACE("Unsupported OPEN statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_FETCH: + MOT_LOG_TRACE("Unsupported FETCH statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_CLOSE: + MOT_LOG_TRACE("Unsupported CLOSE statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_COMMIT: + MOT_LOG_TRACE("Unsupported COMMIT statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + case PLPGSQL_STMT_ROLLBACK: + MOT_LOG_TRACE("Unsupported ROLLBACK statement"); + return JitVisitResult::JIT_VISIT_ERROR; + + default: + MOT_LOG_TRACE("Unsupported unknown statement: %u", stmt->cmd_type); + return JitVisitResult::JIT_VISIT_ERROR; + } + } + return JitVisitResult::JIT_VISIT_CONTINUE; +} + +static JitVisitResult VisitStmtBlockQueries(PLpgSQL_stmt_block* stmtBlock, JitFunctionQueryVisitor* visitor, int* index) +{ + JitVisitResult result = VisitStmtListQueries(stmtBlock->body, visitor, index); + PROCESS_VISIT_RESULT("STATEMENT block body", stmtBlock->lineno); + if (stmtBlock->exceptions && (list_length(stmtBlock->exceptions->exc_list) > 0)) { + ListCell* lc = nullptr; + foreach (lc, stmtBlock->exceptions->exc_list) { + PLpgSQL_exception* exceptionBlock = (PLpgSQL_exception*)lfirst(lc); + result = VisitStmtListQueries(exceptionBlock->action, visitor, index); + PROCESS_VISIT_RESULT("STATEMENT block exception body", exceptionBlock->lineno); + } + } + return result; +} + +extern bool VisitFunctionQueries(PLpgSQL_function* function, JitFunctionQueryVisitor* visitor) +{ + int index = 0; + if (function->action != nullptr) { + JitVisitResult result = VisitStmtBlockQueries(function->action, visitor, &index); + if (result == JitVisitResult::JIT_VISIT_ERROR) { + MOT_LOG_TRACE("Failed to visit function"); + return false; + } else if (result == JitVisitResult::JIT_VISIT_STOP) { + MOT_LOG_TRACE("Visit function stopped early by visitor"); + } + } + return true; +} + +int CountFunctionQueries(PLpgSQL_function* function) +{ + int queryCount = 0; + MOT_LOG_TRACE("Counting function queries"); + JitFunctionQueryCounter queryCounter; + if (!VisitFunctionQueries(function, &queryCounter)) { + // we already passed verification, so this can't happen + MOT_ASSERT(false); + MOT_LOG_TRACE("Failed to count number of queries (probable internal error)"); + } else { + queryCount = queryCounter.GetQueryCount(); + MOT_LOG_TRACE("Counted %d function queries", queryCount); + } + return queryCount; +} + +static bool IsPLpgSQLFunction(Oid functionOid) +{ + // search function in pg_proc + HeapTuple procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + if ((procTuple == nullptr) || !HeapTupleIsValid(procTuple)) { + MOT_LOG_TRACE("Cannot determine funciton type: Oid %u not found in pg_proc", functionOid); + return false; + } + + Form_pg_proc procStruct = (Form_pg_proc)GETSTRUCT(procTuple); + MOT_LOG_TRACE("Function %u language id: %u", (unsigned)functionOid, (unsigned)procStruct->prolang) + + // get object id for PLPGSQL validator function + const char* language = "plpgsql"; + HeapTuple languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); + if ((languageTuple == nullptr) || !HeapTupleIsValid(languageTuple)) { + MOT_LOG_TRACE("Cannot find plpgsql language tuple"); + ReleaseSysCache(procTuple); + return false; + } + + bool result = false; + Oid languageOid = HeapTupleGetOid(languageTuple); + ReleaseSysCache(languageTuple); + MOT_LOG_TRACE("Retrieved PLPGSQL language id %u", (unsigned)languageOid); + if (procStruct->prolang != languageOid) { + MOT_LOG_TRACE("Function %u is not a PLpgSQL function", (unsigned)functionOid); + } else { + result = true; + MOT_LOG_TRACE("Function %u is a PLpgSQL function", (unsigned)functionOid); + } + ReleaseSysCache(procTuple); + return result; +} + +bool IsFunctionSupported(FuncExpr* funcExpr) +{ + if (!IsTypeSupported(funcExpr->funcresulttype)) { + MOT_LOG_TRACE( + "Disqualifying function expression: result type %d is unsupported", (int)funcExpr->funcresulttype); + return false; + } + + if (list_length(funcExpr->args) > MOT_JIT_MAX_FUNC_EXPR_ARGS) { + MOT_LOG_TRACE("Unsupported function %d: too many arguments", funcExpr->funcid); + return false; + } + + // each argument is checked separately by T_List node tag below + return true; +} + +static bool IsSimpleNode(Node* node, bool* failed, bool* isConst = nullptr, Oid* constType = nullptr) +{ + if (node == nullptr) { + return true; + } + + switch (nodeTag(node)) { + case T_Const: + if (isConst != nullptr) { + *isConst = true; + } + if (constType != nullptr) { + *constType = ((Const*)node)->consttype; + } + return true; + + case T_Param: + return true; + + case T_FuncExpr: { + FuncExpr* expr = (FuncExpr*)node; + if (expr->funcretset) { + *failed = true; + MOT_LOG_TRACE("Unsupported function returning set"); + return false; + } + if (!IsSimpleNode((Node*)expr->args, failed)) { + MOT_LOG_TRACE("Unsupported function arguments"); + return false; + } + if (!IsFunctionSupported(expr)) { + *failed = true; + MOT_LOG_TRACE("Unsupported function expression"); + return false; + } + // we currently disqualify hybrid expressions, either involving jittable or non-jittable function call + if (HasJitFunctionSource(expr->funcid) || IsPLpgSQLFunction(expr->funcid)) { + *failed = true; + MOT_LOG_TRACE("Unsupported hybrid expression with function call %u", (unsigned)expr->funcid); + return false; + } + return true; + } + + case T_OpExpr: { + OpExpr* expr = (OpExpr*)node; + if (expr->opretset) { + MOT_LOG_TRACE("Unsupported operator returning set"); + return false; + } + if (!IsSimpleNode((Node*)expr->args, failed)) { + MOT_LOG_TRACE("Unsupported operator arguments"); + return false; + } + return true; + } + + case T_RelabelType: + return IsSimpleNode((Node*)((RelabelType*)node)->arg, failed); + + case T_List: { + List* expr = (List*)node; + ListCell* l = nullptr; + foreach (l, expr) { + if (!IsSimpleNode((Node*)lfirst(l), failed)) { + MOT_LOG_TRACE("Unsupported list node"); + return false; + } + } + return true; + } + + case T_ArrayRef: + *failed = true; + MOT_LOG_TRACE("Unsupported ARRAY-REF Node"); + return false; + + case T_DistinctExpr: + MOT_LOG_TRACE("Simple DISTINCT expression"); + return true; + + case T_NullIfExpr: + MOT_LOG_TRACE("Simple NULL-IF expression"); + return true; + + case T_ScalarArrayOpExpr: + MOT_LOG_TRACE("Simple SCALAR-ARRAY-OP expression"); + return true; + + case T_BoolExpr: + MOT_LOG_TRACE("Simple BOOL expression"); + return true; + + case T_FieldSelect: + *failed = true; + MOT_LOG_TRACE("Unsupported FIELD-SELECT expression"); + return false; + + case T_FieldStore: + *failed = true; + MOT_LOG_TRACE("Unsupported FIELD-STORE expression"); + return false; + + case T_CoerceViaIO: + *failed = true; + MOT_LOG_TRACE("Unsupported COERCE-VIA-IO Node"); + return false; + + case T_ArrayCoerceExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported ARRAY-COERCE-EXPR Node"); + return false; + + case T_ConvertRowtypeExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported CONVERT-ROWTYPE-EXPR Node"); + return false; + + case T_CaseExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported CASE-EXPR Node"); + return false; + + case T_CaseWhen: + *failed = true; + MOT_LOG_TRACE("Unsupported CASE-WHEN Node"); + return false; + + case T_CaseTestExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported CASE-TEST Node"); + return false; + + case T_ArrayExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported ARRAY-EXPR Node"); + return false; + + case T_RowExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported ROW-EXPR Node"); + return false; + + case T_RowCompareExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported ROW-COMPARE-EXPR Node"); + return false; + + case T_CoalesceExpr: + MOT_LOG_TRACE("Simple COALESCE-EXPR Node"); + return false; + + case T_MinMaxExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported MINMAX-EXPR Node"); + return false; + + case T_XmlExpr: + *failed = true; + MOT_LOG_TRACE("Unsupported XML-EXPR Node"); + return false; + + case T_NullTest: + MOT_LOG_TRACE("Simple NULL-TEST Node"); + return true; + + case T_HashFilter: + *failed = true; + MOT_LOG_TRACE("Unsupported HASH-FILTER Node"); + return false; + + case T_BooleanTest: + MOT_LOG_TRACE("Simple BOOLEAN-TEST Node"); + return true; + + case T_CoerceToDomain: + *failed = true; + MOT_LOG_TRACE("Unsupported COERCE-TO-DOMAIN Node"); + return false; + + case T_CoerceToDomainValue: + *failed = true; + MOT_LOG_TRACE("Unsupported COERCE-TO-DOMAIN-VALUE Node"); + return false; + + default: + *failed = true; + MOT_LOG_TRACE("Unsupported Node type: %d", (int)nodeTag(node)); + return false; + } +} + +static bool IsSimpleExpr(CachedPlan* cplan, bool* failed, bool* isConst = nullptr, Oid* constType = nullptr) +{ + // 1. There must be one single plan tree + if (list_length(cplan->stmt_list) != 1) { + return false; + } + PlannedStmt* stmt = (PlannedStmt*)linitial(cplan->stmt_list); + + // 2. It must be a RESULT plan --> no scan's required + if (!IsA(stmt, PlannedStmt) || (stmt->commandType != CMD_SELECT)) { + return false; + } + Plan* plan = stmt->planTree; + if (!IsA(plan, BaseResult)) { + return false; + } + + // 3. Can't have any sub-plan or qual clause, either + if ((plan->lefttree != nullptr) || (plan->righttree != nullptr) || (plan->initPlan != nullptr) || + (plan->qual != nullptr) || (((BaseResult*)plan)->resconstantqual != nullptr)) { + return false; + } + + // 4. The plan must have a single attribute as result + if (list_length(plan->targetlist) != 1) { + return false; + } + TargetEntry* tle = (TargetEntry*)linitial(plan->targetlist); + + // 5. First check special case: maybe this is a function call into a jitted function + if (tle->expr->type == T_FuncExpr) { + FuncExpr* funcExpr = (FuncExpr*)tle->expr; + if (HasJitFunctionSource(funcExpr->funcid)) { + // this is a jitted function invocation + MOT_LOG_TRACE("Found non-simple invocation to jitted function %u", (unsigned)funcExpr->funcid); + return false; + } + // since JIT-compilation is postponed until PREPARE, we may have not yet compiled this function, so we also + // check whether it is a PLpgSQL stored procedure + if (IsPLpgSQLFunction(funcExpr->funcid)) { + // this is a jitted function function that we nhave never attempted to compile + MOT_LOG_TRACE("Found non-simple invocation to PLPGsql stored procedure %u", (unsigned)funcExpr->funcid); + return false; + } + } + + // 6. Check that all the nodes in the expression are non-scary. + if (!IsSimpleNode((Node*)tle->expr, failed, isConst, constType)) { + return false; + } + + return true; +} + +bool IsSimpleExpr( + SPIPlanPtr spiPlan, Query* query, bool* failed, bool* isConst /* = nullptr */, Oid* constType /* = nullptr */) +{ + *failed = false; + // It must be a plain SELECT query without any input tables + if (!IsA(query, Query) || (query->commandType != CMD_SELECT) || (query->rtable != NIL)) { + return false; + } + + // Can't have any sub-plans, aggregates, qual clauses either + bool hasComplexClause = (query->hasAggs || query->hasWindowFuncs || query->hasSubLinks || query->hasForUpdate || + query->cteList || query->jointree->quals || query->groupClause || query->havingQual || + query->windowClause || query->distinctClause || query->sortClause || query->limitOffset || + query->limitCount || query->setOperations); + if (hasComplexClause) { + return false; + } + + // The query must have a single attribute as result + if (list_length(query->targetList) != 1) { + return false; + } + + // OK, it seems worth constructing a plan for more careful checking + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile CachedPlan* cplan = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile bool result = false; + PG_TRY(); + { + cplan = SPI_plan_get_cached_plan(spiPlan); + if (cplan == nullptr) { + MOT_LOG_TRACE("Failed to determine expression type - unable to get cached plan"); + *failed = true; + } else { + // Share the remaining work with recheck code path + result = IsSimpleExpr((CachedPlan*)cplan, failed, isConst, constType); + + // Release our plan ref-count + ReleaseCachedPlan((CachedPlan*)cplan, false); + } + } + PG_CATCH(); + { + CurrentMemoryContext = origCxt; + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while retrieving cached plan: %s", edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while retrieving cached plan: %s", edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + return result; +} + +bool IsArgParam(PLpgSQL_function* func, int index, int* argIndex) +{ + *argIndex = -1; + int argCount = func->fn_nargs; + for (int i = 0; i < argCount; ++i) { + if (func->fn_argvarnos[i] == index) { + *argIndex = i; + return true; + } + } + return false; +} + +TupleDesc PrepareTupleDescFromRow(PLpgSQL_row* row, PLpgSQL_function* func) +{ + TupleDesc tupDesc = CreateTemplateTupleDesc(row->nfields, false); + for (int i = 0; i < row->nfields; i++) { + int varNo = row->varnos[i]; + PLpgSQL_datum* datum = func->datums[varNo]; + if (datum->dtype == PLPGSQL_DTYPE_VAR) { + PLpgSQL_var* var = (PLpgSQL_var*)datum; + TupleDescInitEntry(tupDesc, i + 1, var->refname, var->datatype->typoid, var->datatype->atttypmod, 0); + TupleDescInitEntryCollation(tupDesc, i + 1, var->datatype->collation); + } + } + return tupDesc; +} + +extern SPIPlanPtr GetSpiPlan(PLpgSQL_function* function, PLpgSQL_expr* expr) +{ + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(expr, function, &attrs)) { + MOT_LOG_TRACE("Failed to get query attributes for query: %s", expr->query); + return nullptr; + } + + SPIPlanPtr plan = nullptr; + if (attrs.m_spiPlan == nullptr) { + MOT_LOG_TRACE("Query plan is null: %s", expr->query); + } else { + int rc = SPI_keepplan(attrs.m_spiPlan); + if (rc != 0) { + MOT_LOG_TRACE("Failed to keep SPI plan: %s (%d)", SPI_result_code_string(rc), rc); + } else { + // move the plan to the result + plan = attrs.m_spiPlan; + attrs.m_spiPlan = nullptr; + } + } + CleanupExprQueryAttrs(&attrs); + return plan; +} + +extern SPIPlanPtr PrepareSpiPlan(JitFunctionContext* functionContext, int exprIndex, PLpgSQL_expr** expr) +{ + PLpgSQL_function* function = ((JitFunctionExecState*)functionContext->m_execState)->m_function; + MOT_ASSERT(function != nullptr); + + SPIAutoConnect spiAutoConnect; + if (!spiAutoConnect.IsConnected()) { + int rc = spiAutoConnect.GetErrorCode(); + MOT_LOG_TRACE("Failed to connect to SPI while generating plan for SP non-jittable expression %u: %s (%u)", + exprIndex, + SPI_result_code_string(rc), + rc); + return nullptr; + } + + // NOTE: every variable used after catch needs to be volatile (see longjmp() man page) + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile SPIPlanPtr result = nullptr; + JitSubQueryPlanGenerator subqPlanGen((PLpgSQL_function*)function, expr, exprIndex); + PG_TRY(); + { + if (!VisitFunctionQueries((PLpgSQL_function*)function, &subqPlanGen)) { + MOT_LOG_TRACE("Failed to traverse function while generating plan for expression %u", exprIndex); + } else { + result = subqPlanGen.GetPlan(); + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while generating plan for non-jittable SP sub-query: %s", edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + if (result == nullptr) { + MOT_LOG_TRACE("Could not find plan for expression with index %u", exprIndex); + } + return result; +} + +void DestroyCallSitePlan(JitCallSitePlan* callSitePlan) +{ + if (callSitePlan->m_queryPlan != nullptr) { + JitDestroyPlan(callSitePlan->m_queryPlan); + callSitePlan->m_queryPlan = nullptr; + } + if (callSitePlan->m_spiPlan != nullptr) { + (void)SPI_freeplan(callSitePlan->m_spiPlan); + callSitePlan->m_spiPlan = nullptr; + } + if (callSitePlan->m_callParamInfo != nullptr) { + MOT::MemSessionFree(callSitePlan->m_callParamInfo); + callSitePlan->m_callParamInfo = nullptr; + } + if (callSitePlan->m_queryString != nullptr) { + MOT::MemSessionFree(callSitePlan->m_queryString); + callSitePlan->m_queryString = nullptr; + } + if (callSitePlan->m_tupDesc != nullptr) { + FreeTupleDesc(callSitePlan->m_tupDesc); + callSitePlan->m_tupDesc = nullptr; + } +} + +static bool ValidateFuncArgList(List* argList) +{ + ListCell* lc = nullptr; + int i = 0; + foreach (lc, argList) { + Expr* arg = (Expr*)lfirst(lc); + if (IsA(arg, NamedArgExpr)) { + NamedArgExpr* namedArg = (NamedArgExpr*)arg; + MOT_LOG_TRACE("Rejecting invoke query with named argument %d: %s", i, namedArg->name); + return false; + } + } + return true; +} + +FuncExpr* ValidateDirectInvokePlan(Query* query) +{ + if ((query->jointree) && (query->jointree->fromlist || query->jointree->quals)) { + MOT_LOG_TRACE("ValidateDirectInvokePlan(): Disqualifying invoke query - FROM clause is not empty"); + return nullptr; + } + if (list_length(query->targetList) != 1) { + MOT_LOG_TRACE( + "ValidateDirectInvokePlan(): Disqualifying invoke query - target list does not contain exactly one entry"); + return nullptr; + } + + TargetEntry* targetEntry = (TargetEntry*)linitial(query->targetList); + if (nodeTag(targetEntry->expr) != T_FuncExpr) { + MOT_LOG_TRACE("ValidateDirectInvokePlan(): Disqualifying invoke query - single target entry is not a function " + "expression"); + return nullptr; + } + FuncExpr* result = (FuncExpr*)targetEntry->expr; + if (!ValidateFuncArgList(result->args)) { + result = nullptr; + } + return result; +} + +FuncExpr* ValidateSelectInvokePlan(Query* query) +{ + RangeTblEntry* tableEntry = (RangeTblEntry*)linitial(query->rtable); + if (tableEntry->rtekind != RTE_FUNCTION) { + MOT_LOG_TRACE("ValidateSelectInvokePlan(): Disqualifying invoke query - table entry type is not FUNCTION"); + return nullptr; + } + if (nodeTag(tableEntry->funcexpr) != T_FuncExpr) { + MOT_LOG_TRACE( + "ValidateSelectInvokePlan(): Disqualifying invoke query - invalid table entry function expression type"); + return nullptr; + } + FuncExpr* result = (FuncExpr*)tableEntry->funcexpr; + if (!ValidateFuncArgList(result->args)) { + result = nullptr; + } + return result; +} + +bool IsTargetListSingleRecordFunc(List* targetList) +{ + bool result = false; + if (list_length(targetList) == 1) { + TargetEntry* targetEntry = (TargetEntry*)linitial(targetList); + if (nodeTag(targetEntry->expr) == T_FuncExpr) { + FuncExpr* expr = (FuncExpr*)targetEntry->expr; + if (expr->funcresulttype == RECORDOID) { + result = true; + } + } + } + return result; +} + +bool IsCallSiteJittedFunction(JitCallSite* callSite) +{ + bool result = false; + if ((callSite->m_queryContext != nullptr) && (callSite->m_queryContext->m_commandType == JIT_COMMAND_INVOKE)) { + JitQueryContext* queryContext = (JitQueryContext*)callSite->m_queryContext; + if (queryContext->m_invokeContext != nullptr) { + result = true; + } + } + MOT_LOG_DEBUG("Call-site is composite: %s", result ? "true" : "false"); + return result; +} + +static bool IsCallSitePlanJittedFunction(JitCallSitePlan* callSitePlan) +{ + bool result = false; + if ((callSitePlan->m_queryPlan != nullptr) && (callSitePlan->m_queryPlan->_command_type == JIT_COMMAND_INVOKE)) { + JitInvokePlan* invokePlan = (JitInvokePlan*)callSitePlan->m_queryPlan; + if (invokePlan->m_functionPlan != nullptr) { + result = true; + } + } + MOT_LOG_DEBUG("Call-site is jitted function: %s", result ? "true" : "false"); + return result; +} + +static uint8_t IsUnjittableInvokeQuery(JitCallSitePlan* callSitePlan, Query* query) +{ + uint8_t result = 0; + if (callSitePlan->m_queryPlan == nullptr) { + FuncExpr* funcExpr = nullptr; + int tableCount = list_length(query->rtable); + if (tableCount == 0) { + funcExpr = ValidateDirectInvokePlan(query); + result = 1; + } else if (tableCount == 1) { + funcExpr = ValidateSelectInvokePlan(query); + } + if (funcExpr != nullptr) { + result = 1; + } + } + return result; +} + +static uint8_t IsModStmt(Query* query) +{ + uint8_t modStmt = 0; + if (query->canSetTag) { + if (query->commandType == CMD_INSERT || query->commandType == CMD_UPDATE || query->commandType == CMD_DELETE || + query->commandType == CMD_MERGE) { + modStmt = 1; + } + } + return modStmt; +} + +static bool PrepareCallParamInfo(JitCallSitePlan* callSitePlan, FuncParamInfoList* paramInfoList, + PLpgSQL_function* function, const ExprQueryAttrs& attrs, char* queryString) +{ + MOT_LOG_TRACE("Creating call param info of size %d for sub-query: %s", paramInfoList->m_numParams, queryString); + callSitePlan->m_callParamCount = bms_num_members(attrs.m_paramNos); + if (callSitePlan->m_callParamCount > 0) { + size_t allocSize = sizeof(JitCallParamInfo) * callSitePlan->m_callParamCount; + callSitePlan->m_callParamInfo = (JitCallParamInfo*)MOT::MemSessionAlloc(allocSize); + if (callSitePlan->m_callParamInfo == nullptr) { + MOT_LOG_ERROR( + "Failed to allocate %u bytes for %d call parameters", allocSize, callSitePlan->m_callParamCount); + return false; + } + errno_t erc = memset_s(callSitePlan->m_callParamInfo, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + } else { + callSitePlan->m_callParamInfo = nullptr; + } + + // setup only used parameters + int callParamIndex = 0; + if (attrs.m_paramNos && !bms_is_empty(attrs.m_paramNos)) { + int paramId = bms_next_member(attrs.m_paramNos, -1); + while (paramId >= 0) { + MOT_ASSERT(paramId < paramInfoList->m_numParams); + MOT_ASSERT(callParamIndex < callSitePlan->m_callParamCount); + callSitePlan->m_callParamInfo[callParamIndex].m_paramIndex = paramId; + callSitePlan->m_callParamInfo[callParamIndex].m_paramType = paramInfoList->m_paramInfo[paramId].m_paramType; + int datumIndex = paramInfoList->m_paramInfo[paramId].m_datumId; + int argIndex = -1; + // for SP parameters we save parameter index, and for locals we save datum index + if (IsArgParam(function, datumIndex, &argIndex)) { + MOT_LOG_TRACE("Query param %d, mapped to datum %d, is argument with original index %d", + paramId, + datumIndex, + argIndex); + callSitePlan->m_callParamInfo[callParamIndex].m_paramKind = JitCallParamKind::JIT_CALL_PARAM_ARG; + callSitePlan->m_callParamInfo[callParamIndex].m_invokeArgIndex = argIndex; + callSitePlan->m_callParamInfo[callParamIndex].m_invokeDatumIndex = -1; + } else { + MOT_LOG_TRACE("Query param %d, mapped to datum %d, is local var", paramId, datumIndex); + callSitePlan->m_callParamInfo[callParamIndex].m_paramKind = JitCallParamKind::JIT_CALL_PARAM_LOCAL; + callSitePlan->m_callParamInfo[callParamIndex].m_invokeArgIndex = -1; + callSitePlan->m_callParamInfo[callParamIndex].m_invokeDatumIndex = datumIndex; + } + paramId = bms_next_member(attrs.m_paramNos, paramId); + ++callParamIndex; + } + } + + return true; +} + +static bool PrepareStmtQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, FuncParamInfoList* paramInfoList, + PLpgSQL_function* function, JitCallSitePlan* callSitePlanList, int queryCount, int exprIndex, bool into, + int* queryIndex) +{ + char* queryString = expr->query; + + // parse query and try to check if jittable and then create code for it + ExprQueryAttrs attrs; + if (!GetExprQueryAttrs(expr, function, &attrs)) { + MOT_LOG_TRACE("Failed to get query attributes from expression: %s", expr->query); + return false; + } + MOT_LOG_TRACE("Preparing function query: %s", queryString); + + // we now check for a simple expression, which is OK + bool failed = false; + bool isSimpleExpr = IsSimpleExpr(attrs.m_spiPlan, attrs.m_query, &failed); + if (failed) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Failed to prepare query - cannot determine expression type"); + return false; + } + if (isSimpleExpr) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Found simple expression: %s", queryString); + return true; + } + + // at this point, if there are no vacant slots in the call site list, then we have a bug in query counting + JitCallSitePlan* callSitePlan = &callSitePlanList[*queryIndex]; + if (*queryIndex >= queryCount) { + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Missing vacant slot in call site plan list"); + return false; + } + + // prepare called query plan (in case of non-jittable MOT query, we prepare a call site without a query plan) + callSitePlan->m_queryPlan = IsJittableQuery(attrs.m_query, queryString, true); + if (callSitePlan->m_queryPlan == nullptr) { + // if this is an MOT query we still continue with unjittable call site, otherwise we disqualify + // stored-procedure with non-MOT query + MOT_LOG_TRACE("Sub-query is not jittable, checking for MOT query: %s", queryString); + StorageEngineType storageEngineType = SE_TYPE_UNSPECIFIED; + CheckTablesStorageEngine(attrs.m_query, &storageEngineType); + if (storageEngineType != SE_TYPE_MOT) { + DestroyCallSitePlan(callSitePlan); + CleanupExprQueryAttrs(&attrs); + MOT_LOG_TRACE("Sub-query is not MOT query (%d): %s", (int)storageEngineType, queryString); + return false; + } + } + + // prepare call parameters + if (!PrepareCallParamInfo(callSitePlan, paramInfoList, function, attrs, queryString)) { + MOT_LOG_ERROR("Failed to prepare call parameters for sub-query: %s", queryString); + DestroyCallSitePlan(callSitePlan); + CleanupExprQueryAttrs(&attrs); + return false; + } + + // set new attributes + callSitePlan->m_queryString = DupString(queryString, JitContextUsage::JIT_CONTEXT_LOCAL); + if (callSitePlan->m_queryString == nullptr) { + MOT_LOG_ERROR("Failed to duplicate called query string: %s", queryString); + DestroyCallSitePlan(callSitePlan); + CleanupExprQueryAttrs(&attrs); + return false; // safe cleanup + } + + // save the expression for later parsing, it will be still valid by the time we reach code-generation phase + callSitePlan->m_expr = expr; + callSitePlan->m_exprIndex = exprIndex; + MOT_LOG_TRACE("Assigned call site plan %d expression %p at index %d with query %s", + *queryIndex, + expr, + exprIndex, + expr->query); + + // we keep this also for jittable sub-query as it might evolve into unjittable after revalidation + callSitePlan->m_queryCmdType = attrs.m_query->commandType; + + // prepare a global tuple descriptor + MemoryContext oldCtx = MemoryContextSwitchTo(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); + if (IsTargetListSingleRecordFunc(attrs.m_query->targetList) && IsCallSitePlanJittedFunction(callSitePlan)) { + // this is a call into a jitted function returning record (without "* from"), so we build tuple desc according + // to the row datum receiving the result of this call query (so that composite tuple is not used in this case) + if (row != nullptr) { + callSitePlan->m_tupDesc = PrepareTupleDescFromRow(row, function); + } else { + MOT_LOG_TRACE("PrepareStmtQuery(): Disqualifying query - missing row when query returns record"); + DestroyCallSitePlan(callSitePlan); + CleanupExprQueryAttrs(&attrs); + return false; + } + } else { + callSitePlan->m_tupDesc = ExecCleanTypeFromTL(attrs.m_query->targetList, false); + } + (void)MemoryContextSwitchTo(oldCtx); + if (callSitePlan->m_tupDesc == nullptr) { + MOT_LOG_TRACE("Failed to create stored-procedure sub-query tuple descriptor from target list"); + DestroyCallSitePlan(callSitePlan); + CleanupExprQueryAttrs(&attrs); + return false; // safe cleanup during destroy + } + Assert(callSitePlan->m_tupDesc->tdrefcount == -1); + callSitePlan->m_isUnjittableInvoke = IsUnjittableInvokeQuery(callSitePlan, attrs.m_query); + callSitePlan->m_isModStmt = IsModStmt(attrs.m_query); + callSitePlan->m_isInto = static_cast(into); + callSitePlan->m_spiPlan = attrs.m_spiPlan; + attrs.m_spiPlan = nullptr; // keep plan alive + ++(*queryIndex); + CleanupExprQueryAttrs(&attrs); + return true; +} + +JitVisitResult JitFunctionQueryCounter::OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) +{ + MOT_LOG_TRACE("Encountered expression %d at %p with query: %s", index, expr, expr->query); + MOT_ASSERT(m_count == index); + ++m_count; + return JitVisitResult::JIT_VISIT_CONTINUE; +} + +void JitFunctionQueryCounter::OnError(const char* stmtName, int lineNo) +{ + // impossible to arrive here, we just passed validation + MOT_ASSERT(false); + MOT_LOG_TRACE("Encountered error in '%s' statement at line %d, while counting queries", stmtName, lineNo); +} + +JitVisitResult JitFunctionQueryCollector::OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) +{ + bool result = PrepareStmtQuery( + expr, row, m_paramInfoList, m_function, m_callSitePlanList, m_queryCount, index, into, &m_actualQueryCount); + return (result ? JitVisitResult::JIT_VISIT_CONTINUE : JitVisitResult::JIT_VISIT_ERROR); +} + +void JitFunctionQueryCollector::OnError(const char* stmtType, int lineNo) +{ + MOT_LOG_TRACE("Unsupported %s at line %d", stmtType, lineNo); +} + +JitVisitResult JitFunctionQueryVerifier::OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) +{ + // during planning we consider every expression as jittable, it will fail during code-generation if it is not + // supported + return JitVisitResult::JIT_VISIT_CONTINUE; +} + +void JitFunctionQueryVerifier::OnError(const char* stmtType, int lineNo) +{ + MOT_LOG_TRACE("Failed to verify query in '%s' statement at line number %d", stmtType, lineNo); +} + +JitVisitResult JitSubQueryPlanGenerator::OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) +{ + if (index != m_exprIndex) { + return JitVisitResult::JIT_VISIT_CONTINUE; + } + + MOT_LOG_TRACE("Generating SPI plan from expression %d at %p for query: %s", index, expr, expr->query); + m_plan = GetSpiPlan(m_function, expr); + if (m_plan == nullptr) { + return JitVisitResult::JIT_VISIT_ERROR; + } + *m_expr = expr; + return JitVisitResult::JIT_VISIT_STOP; +} + +void JitSubQueryPlanGenerator::OnError(const char* stmtType, int lineNo) +{ + MOT_LOG_TRACE("Failed to generate SP query plan, during statement '%s' at line %d", stmtType, lineNo); +} +} // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.h b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.h new file mode 100644 index 000000000..7b8b7a17f --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_plan_sp_expr.h + * JIT SP execution plan expression helpers. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_plan_sp_expr.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef JIT_PLAN_SP_EXPR_H +#define JIT_PLAN_SP_EXPR_H + +#include "utils/plpgsql.h" +#include "nodes/parsenodes.h" +#include "jit_plan.h" + +namespace JitExec { +struct FuncParamInfo { + Oid m_paramType; + char* m_paramName; + int m_datumId; +}; + +struct FuncParamInfoList { + int m_numParams; + FuncParamInfo* m_paramInfo; +}; + +enum class JitVisitResult { JIT_VISIT_CONTINUE, JIT_VISIT_STOP, JIT_VISIT_ERROR }; + +class JitFunctionQueryVisitor { +public: + JitFunctionQueryVisitor() + {} + + virtual ~JitFunctionQueryVisitor() + {} + + virtual JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) = 0; + + virtual void OnError(const char* stmtType, int lineNo) = 0; +}; + +class JitFunctionQueryCounter : public JitFunctionQueryVisitor { +public: + JitFunctionQueryCounter() : m_count(0) + {} + + ~JitFunctionQueryCounter() final + {} + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) final; + + void OnError(const char* stmtName, int lineNo) final; + + inline int GetQueryCount() const + { + return m_count; + } + +private: + int m_count; +}; + +class JitFunctionQueryCollector : public JitFunctionQueryVisitor { +public: + JitFunctionQueryCollector( + FuncParamInfoList* paramInfoList, PLpgSQL_function* function, JitCallSitePlan* callSitePlanList, int queryCount) + : m_paramInfoList(paramInfoList), + m_function(function), + m_callSitePlanList(callSitePlanList), + m_queryCount(queryCount), + m_actualQueryCount(0) + {} + + ~JitFunctionQueryCollector() final + { + m_paramInfoList = nullptr; + m_function = nullptr; + m_callSitePlanList = nullptr; + } + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) final; + + void OnError(const char* stmtType, int lineNo) final; + + inline int GetActualQueryCount() const + { + return m_actualQueryCount; + } + +private: + FuncParamInfoList* m_paramInfoList; + PLpgSQL_function* m_function; + JitCallSitePlan* m_callSitePlanList; + int m_queryCount; + int m_actualQueryCount; +}; + +class JitFunctionQueryVerifier : public JitFunctionQueryVisitor { +public: + JitFunctionQueryVerifier() + {} + + ~JitFunctionQueryVerifier() final + {} + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) final; + + void OnError(const char* stmtType, int lineNo) final; +}; + +class JitSubQueryPlanGenerator : public JitFunctionQueryVisitor { +public: + JitSubQueryPlanGenerator(PLpgSQL_function* function, PLpgSQL_expr** expr, int exprIndex) + : m_function(function), m_expr(expr), m_exprIndex(exprIndex), m_plan(nullptr) + {} + + ~JitSubQueryPlanGenerator() final + { + m_function = nullptr; + m_expr = nullptr; + m_plan = nullptr; + } + + JitVisitResult OnQuery(PLpgSQL_expr* expr, PLpgSQL_row* row, int index, bool into) final; + + void OnError(const char* stmtType, int lineNo) final; + + inline SPIPlanPtr GetPlan() + { + return m_plan; + } + +private: + PLpgSQL_function* m_function; + PLpgSQL_expr** m_expr; + int m_exprIndex; + SPIPlanPtr m_plan; +}; + +extern bool PrepareFuncParamInfoList(PLpgSQL_function* function, FuncParamInfoList* paramInfoList); +extern void DestroyFuncParamInfoList(FuncParamInfoList* paramInfoList); + +extern int CountFunctionQueries(PLpgSQL_function* function); + +extern bool IsSimpleExpr( + SPIPlanPtr spiPlan, Query* query, bool* failed, bool* isConst = nullptr, Oid* constType = nullptr); + +extern bool IsArgParam(PLpgSQL_function* func, int index, int* argIndex); + +extern TupleDesc PrepareTupleDescFromRow(PLpgSQL_row* row, PLpgSQL_function* func); + +/** @brief Prepare SPI plan for a function expression by index. */ +extern SPIPlanPtr PrepareSpiPlan(JitExec::JitFunctionContext* functionContext, int exprIndex, PLpgSQL_expr** expr); + +/** @brief Prepare SPI plan for a function expression. */ +extern SPIPlanPtr GetSpiPlan(PLpgSQL_function* function, PLpgSQL_expr* expr); + +extern void DestroyCallSitePlan(JitCallSitePlan* callSitePlan); + +extern FuncExpr* ValidateDirectInvokePlan(Query* query); + +extern FuncExpr* ValidateSelectInvokePlan(Query* query); + +extern bool IsTargetListSingleRecordFunc(List* targetList); + +extern bool IsCallSiteJittedFunction(JitCallSite* callSite); + +extern bool IsFunctionSupported(FuncExpr* funcExpr); +} // namespace JitExec + +#endif /* JIT_PLAN_SP_EXPR_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_profiler.cpp b/src/gausskernel/storage/mot/jit_exec/jit_profiler.cpp new file mode 100644 index 000000000..98e328724 --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_profiler.cpp @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_profiler.cpp + * JIT LLVM. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_profiler.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "libintl.h" +#include "jit_profiler.h" + +#include "debug_utils.h" +#include "utilities.h" +#include "thread_id.h" +#include "mot_configuration.h" + +namespace JitExec { +DECLARE_LOGGER(JitProfiler, JitExec) + +JitProfiler* JitProfiler::m_instance = nullptr; + +uint32_t JitProfileFunctionMetaData::GetProfileRegionDataId(const char* name) +{ + ProfileRegionIdMap::iterator itr = m_profileRegionIdMap.find(name); + if (itr == m_profileRegionIdMap.end()) { + uint32_t id = m_profileRegionIdMap.size(); + std::pair pairib = + m_profileRegionIdMap.insert(ProfileRegionIdMap::value_type(name, id)); + MOT_ASSERT(pairib.second); + m_profileRegionIdNameMap[id] = name; + return id; + } + return itr->second; +} + +uint32_t JitProfileFunctionMetaData::FindProfileRegionId(const char* name) const +{ + uint32_t regionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + ProfileRegionIdMap::const_iterator itr = m_profileRegionIdMap.find(name); + if (itr != m_profileRegionIdMap.end()) { + regionId = itr->second; + } + return regionId; +} + +void JitProfileFunctionData::CopySubQueryIdArray(const SubQueryIdArray& rhs) +{ + m_subQueryIdArray.resize(rhs.size()); + (void)std::copy(rhs.begin(), rhs.end(), m_subQueryIdArray.begin()); +} + +void JitProfileFunctionData::CopyMetaData(JitProfileFunctionMetaData* sourceData) +{ + m_functionOid = sourceData->GetFunctionOid(); + m_functionId = sourceData->GetFunctionId(); + m_parentFunctionId = sourceData->GetParentFunctionId(); + m_nameSpace = sourceData->GetNameSpace(); + m_totalRegionId = sourceData->GetTotalRegionId(); + CopySubQueryIdArray(sourceData->GetSubQueryIdArray()); +} + +void JitProfileFunctionData::Accumulate(const JitProfileFunctionData& rhs) +{ + if (m_profileRegionDataArray.size() < rhs.m_profileRegionDataArray.size()) { + m_profileRegionDataArray.resize(rhs.m_profileRegionDataArray.size()); + } + uint32_t regionCount = std::min(m_profileRegionDataArray.size(), rhs.m_profileRegionDataArray.size()); + for (uint32_t i = 0; i < regionCount; ++i) { + m_profileRegionDataArray[i].m_accumulatedTime += rhs.m_profileRegionDataArray[i].m_accumulatedTime; + m_profileRegionDataArray[i].m_sampleCount += rhs.m_profileRegionDataArray[i].m_sampleCount; + } +} + +void JitProfileFunctionData::AccumulateChild(const JitProfileFunctionData& rhs) +{ + // we would like to accumulate the child's total time into the function's net time + if (rhs.m_totalRegionId != MOT_JIT_PROFILE_INVALID_REGION_ID && + rhs.m_totalRegionId < rhs.m_profileRegionDataArray.size()) { + const JitProfileRegionData& regionData = rhs.m_profileRegionDataArray[rhs.m_totalRegionId]; + if (regionData.m_sampleCount > 0) { + // we do not average over child sample count (we have that already in child's report), but rather later on + // average over parent call count - thus reporting the average sum of all child calls in a single parent + // call, when we do this for each jittable child call + m_childNetTime += regionData.m_accumulatedTime; + } + } +} + +void JitProfileFunctionData::Dump() +{ + for (uint32_t i = 0; i < m_profileRegionDataArray.size(); ++i) { + JitProfileRegionData& regionData = m_profileRegionDataArray[i]; + if (regionData.m_sampleCount > 0) { + MOT_LOG_TRACE(" Region %u: sample count %" PRIu64 ", accum %" PRIu64, + i, + regionData.m_sampleCount, + regionData.m_accumulatedTime); + } + } +} + +void JitProfileFunctionData::Report(const char* functionName, const JitProfileFunctionMetaData& profileMetaData) +{ + // calculate total time, always in entry zero + if (!m_profileRegionDataArray.empty() && (m_totalRegionId != MOT_JIT_PROFILE_INVALID_FUNCTION_ID) && + (m_totalRegionId < m_profileRegionDataArray.size())) { + JitProfileRegionData& regionData = m_profileRegionDataArray[m_totalRegionId]; + if (regionData.m_sampleCount > 0) { + uint64_t totalAvgTime = regionData.m_accumulatedTime / regionData.m_sampleCount; + MOT_LOG_DEBUG("Profile report for jitted function: %s", functionName); + for (uint32_t i = 0; i < m_profileRegionDataArray.size(); ++i) { + regionData = m_profileRegionDataArray[i]; + if (regionData.m_sampleCount > 0) { + uint64_t avgTime = regionData.m_accumulatedTime / regionData.m_sampleCount; + uint64_t nanos = MOT::CpuCyclesLevelTime::CyclesToNanoseconds(avgTime); + uint64_t selfPercent = avgTime * 100 / totalAvgTime; + JitProfileFunctionMetaData::ProfileRegionIdNameMap::const_iterator itr = + profileMetaData.GetRegionIdNameMap().find(i); + if (itr != profileMetaData.GetRegionIdNameMap().end()) { + MOT_LOG_DEBUG( + "Region '%s': %" PRIu64 " nano-seconds (%d%%)", itr->second.c_str(), nanos, selfPercent); + } + } + } + } + } +} + +void JitProfileFunctionData::Report( + MotJitProfile* entry, uint32_t defVarsRegionId, uint32_t initVarsRegionId, uint32_t childCallRegionId) +{ + entry->totalTime = 0; + entry->selfTime = 0; + entry->childNetTime = 0; + entry->childGrossTime = 0; + entry->defVarsTime = 0; + entry->initVarsTime = 0; + + // calculate total time, always in entry zero + if (m_totalRegionId < m_profileRegionDataArray.size()) { + JitProfileRegionData& totalRegionData = m_profileRegionDataArray[m_totalRegionId]; + if (totalRegionData.m_sampleCount > 0) { + uint64_t totalCount = totalRegionData.m_sampleCount; + uint64_t totalAvgTime = totalRegionData.m_accumulatedTime / totalCount; + entry->totalTime = (int64)MOT::CpuCyclesLevelTime::CyclesToMicroseconds(totalAvgTime); + entry->selfTime = entry->totalTime; + + uint64_t childNetAvgTime = m_childNetTime / totalCount; + entry->childNetTime = (int64)MOT::CpuCyclesLevelTime::CyclesToMicroseconds(childNetAvgTime); + + if (childCallRegionId < m_profileRegionDataArray.size()) { + JitProfileRegionData& childCallRegionData = m_profileRegionDataArray[childCallRegionId]; + if (childCallRegionData.m_sampleCount > 0) { + // we do not compute normal average for child calls, but rather average of total number of calls, + // this will show the average total time of actual child calls made per function invocation + uint64_t childCallAvgTime = childCallRegionData.m_accumulatedTime / totalCount; + entry->childGrossTime = (int64)MOT::CpuCyclesLevelTime::CyclesToMicroseconds(childCallAvgTime); + entry->selfTime = entry->totalTime - entry->childGrossTime; + } + } + + if (defVarsRegionId < m_profileRegionDataArray.size()) { + JitProfileRegionData& defVarsRegionData = m_profileRegionDataArray[defVarsRegionId]; + if (defVarsRegionData.m_sampleCount > 0) { + uint64_t defVarsAvgTime = defVarsRegionData.m_accumulatedTime / defVarsRegionData.m_sampleCount; + entry->defVarsTime = (int64)MOT::CpuCyclesLevelTime::CyclesToMicroseconds(defVarsAvgTime); + } + } + + if (initVarsRegionId < m_profileRegionDataArray.size()) { + JitProfileRegionData& initVarsRegionData = m_profileRegionDataArray[initVarsRegionId]; + if (initVarsRegionData.m_sampleCount > 0) { + uint64_t initVarsAvgTime = initVarsRegionData.m_accumulatedTime / initVarsRegionData.m_sampleCount; + entry->initVarsTime = (int64)MOT::CpuCyclesLevelTime::CyclesToMicroseconds(initVarsAvgTime); + } + } + } + } +} + +bool JitProfiler::CreateInstance() +{ + if (m_instance == nullptr) { + m_instance = new (std::nothrow) JitProfiler(); + } + return (m_instance != nullptr); +} + +void JitProfiler::DestroyInstance() +{ + if (m_instance != nullptr) { + delete m_instance; + m_instance = nullptr; + } +} + +JitProfiler* JitProfiler::GetInstance() +{ + return m_instance; +} + +uint32_t JitProfiler::GetProfileFunctionId(const char* functionName, Oid functionOid) +{ + uint32_t functionId = MOT_JIT_PROFILE_INVALID_FUNCTION_ID; + QualifiedName qname = std::make_pair(MOT_JIT_GLOBAL_QUERY_NS, functionName); + std::unique_lock lock(m_functionIdLock); + ProfileFunctionMetaDataMap::iterator itr = m_profileFunctionMetaDataMap.find(qname); + if (itr == m_profileFunctionMetaDataMap.end()) { + JitProfileFunctionMetaData* functionData = + new (std::nothrow) JitProfileFunctionMetaData(functionOid, m_nextFunctionId); + if (functionData != nullptr) { + functionId = m_nextFunctionId++; + (void)m_profileFunctionMetaDataMap.insert(ProfileFunctionMetaDataMap::value_type(qname, functionData)); + m_profileFunctionIdNameMap[functionId] = qname; + } else { + MOT_LOG_TRACE("Failed to allocate profile meta-data object: %s", functionName); + } + } else { + functionId = itr->second->GetFunctionId(); + } + return functionId; +} + +uint32_t JitProfiler::GetProfileQueryId(const char* nameSpace, const char* queryString) +{ + uint32_t functionId = MOT_JIT_PROFILE_INVALID_FUNCTION_ID; + uint32_t parentFunctionId = MOT_JIT_PROFILE_INVALID_FUNCTION_ID; + bool expectingParent = (strcmp(nameSpace, MOT_JIT_GLOBAL_QUERY_NS) == 0); + + QualifiedName qname = std::make_pair(nameSpace, queryString); + QualifiedName pqname = std::make_pair(MOT_JIT_GLOBAL_QUERY_NS, nameSpace); + + std::unique_lock lock(m_functionIdLock); + JitProfileFunctionMetaData* parentData = nullptr; + ProfileFunctionMetaDataMap::iterator pitr = m_profileFunctionMetaDataMap.find(pqname); + if (pitr != m_profileFunctionMetaDataMap.end()) { + parentData = pitr->second; + parentFunctionId = pitr->second->GetFunctionId(); + } else if (expectingParent) { + // bail out if expected parent not found + MOT_LOG_TRACE("Parent by name-space %s not found: %s", nameSpace, queryString); + return MOT_JIT_PROFILE_INVALID_FUNCTION_ID; + } + + // search for function + ProfileFunctionMetaDataMap::iterator itr = m_profileFunctionMetaDataMap.find(qname); + if (itr == m_profileFunctionMetaDataMap.end()) { + JitProfileFunctionMetaData* functionData = + new (std::nothrow) JitProfileFunctionMetaData(InvalidOid, m_nextFunctionId, parentFunctionId, nameSpace); + if (functionData != nullptr) { + functionId = m_nextFunctionId++; + (void)m_profileFunctionMetaDataMap.insert(ProfileFunctionMetaDataMap::value_type(qname, functionData)); + (void)m_profileFunctionIdNameMap.insert(ProfileFunctionIdNameMap::value_type(functionId, qname)); + if (parentData != nullptr) { + parentData->AddChildQuery(functionId); + } + } else { + MOT_LOG_TRACE( + "Failed to allocate profile meta-data object with name-space [%s]: %s", nameSpace, queryString); + } + } else { + // profiled query already registered + JitProfileFunctionMetaData* functionData = itr->second; + functionId = functionData->GetFunctionId(); + } + return functionId; +} + +uint32_t JitProfiler::GetProfileRegionId(const char* functionName, const char* regionName) +{ + uint32_t regionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + QualifiedName qname = std::make_pair(MOT_JIT_GLOBAL_QUERY_NS, functionName); + std::unique_lock lock(m_functionIdLock); + ProfileFunctionMetaDataMap::iterator itr = m_profileFunctionMetaDataMap.find(qname); + if (itr != m_profileFunctionMetaDataMap.end()) { + regionId = itr->second->GetProfileRegionDataId(regionName); + } + return regionId; +} + +uint32_t JitProfiler::GetQueryProfileRegionId(const char* nameSpace, const char* queryString, const char* regionName) +{ + uint32_t regionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + QualifiedName qname = std::make_pair(nameSpace, queryString); + std::unique_lock lock(m_functionIdLock); + ProfileFunctionMetaDataMap::iterator itr = m_profileFunctionMetaDataMap.find(qname); + if (itr != m_profileFunctionMetaDataMap.end()) { + regionId = itr->second->GetProfileRegionDataId(regionName); + } + return regionId; +} + +void JitProfiler::EmitProfileData(uint32_t functionId, uint32_t regionId, bool startEvent) +{ + // silently ignore invalid parameters + if ((functionId == MOT_JIT_PROFILE_INVALID_FUNCTION_ID) || (regionId == MOT_JIT_PROFILE_INVALID_REGION_ID)) { + return; + } + + // create on-demand profile data array for current thread + uint32_t threadId = MOTCurrThreadId; + if (m_profileFunctionDataArray[threadId] == nullptr) { + ThreadProfileFunctionDataArray* functionDataArray = new (std::nothrow) ThreadProfileFunctionDataArray(); + if (functionDataArray == nullptr) { + return; + } + + std::unique_lock lock(m_functionIdLock); + // we don't care function id in each entry is zero... + functionDataArray->resize(m_profileFunctionMetaDataMap.size()); + m_profileFunctionDataArray[threadId] = functionDataArray; + } + + // allocate space for profiled function if required (maybe function added after profiling started) + ThreadProfileFunctionDataArray& pdArray = *m_profileFunctionDataArray[threadId]; + if (pdArray.size() <= functionId) { + // we don't care function id in each new entry is zero... + pdArray.resize(functionId + 1); + } + + // emit profile data for region + if (startEvent) { + pdArray[functionId].StartProfileRegionSample(regionId); + } else { + pdArray[functionId].EndProfileRegionSample(regionId); + } +} + +MotJitProfile* JitProfiler::GetProfileReport(uint32_t* num) +{ + if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { + DumpRawData(); + } + ThreadProfileFunctionDataArray globalProfileData; + CollectReportData(globalProfileData); + ProcessReportData(globalProfileData); + + // dump what we just collected + MOT_LOG_TRACE("JIT Profiler raw PROCESSED data dump:"); + for (uint32_t i = 0; i < globalProfileData.size(); ++i) { + MOT_LOG_TRACE(" JIT profiler function %u raw data dump:", i); + MOT_LOG_TRACE(" Function id %u", globalProfileData[i].GetFunctionId()); + MOT_LOG_TRACE(" Parent function id %u", globalProfileData[i].GetParentFunctionId()); + globalProfileData[i].Dump(); + } + + *num = 0; + uint32_t maxSize = globalProfileData.size(); + MotJitProfile* result = (MotJitProfile*)palloc(maxSize * sizeof(MotJitProfile)); + if (result == nullptr) { + return nullptr; + } + + uint32_t count = 0; + for (uint32_t i = 0; i < maxSize; ++i) { + JitProfileFunctionData& pfd = globalProfileData[i]; + if (pfd.GetTotalSampleCount() == 0) { + continue; + } + + result[count].procOid = pfd.GetFunctionOid(); + result[count].id = pfd.GetFunctionId(); + result[count].parentId = pfd.GetParentFunctionId(); + result[count].query = nullptr; + result[count].nameSpace = nullptr; + result[count].weight = (float4)1.0f; + if (pfd.GetParentFunctionId() != MOT_JIT_PROFILE_INVALID_FUNCTION_ID) { + JitProfileFunctionData& parentPfd = globalProfileData[pfd.GetParentFunctionId()]; + uint32_t parentCount = parentPfd.GetTotalSampleCount(); + if (parentCount > 0) { + result[count].weight = ((float4)pfd.GetTotalSampleCount()) / parentCount; + } + } + + uint32_t defVarsRegionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + uint32_t initVarsRegionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + uint32_t childCallRegionId = MOT_JIT_PROFILE_INVALID_REGION_ID; + { + std::unique_lock lock(m_functionIdLock); + ProfileFunctionIdNameMap::iterator itr = m_profileFunctionIdNameMap.find(pfd.GetFunctionId()); + if (itr != m_profileFunctionIdNameMap.end()) { + QualifiedName& qname = itr->second; + if (pfd.GetFunctionOid() != InvalidOid) { + const char* queryString = qname.second.c_str(); + const char* dotPtr = strchr(queryString, '.'); + if (dotPtr == nullptr) { + result[count].query = pstrdup(queryString); + } else { + // we shave off the function id + size_t dotPos = dotPtr - queryString; + result[count].query = pnstrdup(queryString, dotPos); + } + } else { + result[count].query = pstrdup(qname.second.c_str()); + } + result[count].nameSpace = pstrdup(qname.first.c_str()); + ProfileFunctionMetaDataMap::iterator itr2 = m_profileFunctionMetaDataMap.find(qname); + if (itr2 != m_profileFunctionMetaDataMap.end()) { + JitProfileFunctionMetaData* metaData = itr2->second; + defVarsRegionId = metaData->GetDefVarsRegionId(); + initVarsRegionId = metaData->GetInitVarsRegionId(); + childCallRegionId = metaData->GetChildCallRegionId(); + } + } + } + + pfd.Report(&result[count], defVarsRegionId, initVarsRegionId, childCallRegionId); + ++count; + } + + *num = count; + return result; +} + +JitProfiler::JitProfiler() : m_nextFunctionId(0) +{ + m_profileFunctionDataArray.resize(MOT::GetGlobalConfiguration().m_maxThreads); +} + +JitProfiler::~JitProfiler() +{ + if (MOT_CHECK_TRACE_LOG_LEVEL()) { + GenerateReport(); + } + Cleanup(); +} + +void JitProfiler::GenerateReport() +{ + // accumulate all thread data into global thread data + ThreadProfileFunctionDataArray globalProfileData; + CollectReportData(globalProfileData); + ProcessReportData(globalProfileData); + + std::unique_lock lock(m_functionIdLock); + + // generate report + uint32_t functionCount = globalProfileData.size(); + for (uint32_t i = 0; i < functionCount; ++i) { + JitProfileFunctionData& pfd = globalProfileData[i]; + ProfileFunctionIdNameMap::iterator itr = m_profileFunctionIdNameMap.find(pfd.GetFunctionId()); + if (itr != m_profileFunctionIdNameMap.end()) { + QualifiedName& qname = itr->second; + ProfileFunctionMetaDataMap::iterator itr2 = m_profileFunctionMetaDataMap.find(qname); + if (itr2 != m_profileFunctionMetaDataMap.end()) { + JitProfileFunctionMetaData* functionData = itr2->second; + globalProfileData[i].Report(qname.second.c_str(), *functionData); + } + } + } +} + +void JitProfiler::DumpRawData() +{ + MOT_LOG_TRACE("JIT Profiler raw data dump:"); + uint32_t maxThreads = MOT::GetGlobalConfiguration().m_maxThreads; + for (uint32_t i = 0; i < maxThreads; ++i) { + if (m_profileFunctionDataArray[i] != nullptr) { + MOT_LOG_TRACE(" JIT profiler thread %u raw data dump:", i); + ThreadProfileFunctionDataArray& nextArray = *m_profileFunctionDataArray[i]; + for (uint32_t j = 0; j < nextArray.size(); ++j) { + MOT_LOG_TRACE(" JIT profiler function %u raw data dump:", j); + nextArray[j].Dump(); + } + } + } +} + +void JitProfiler::CollectReportData(ThreadProfileFunctionDataArray& globalProfileData) +{ + uint32_t functionCount = 0; + { + std::unique_lock lock(m_functionIdLock); + functionCount = m_profileFunctionMetaDataMap.size(); + } + globalProfileData.resize(functionCount); + uint32_t maxThreads = MOT::GetGlobalConfiguration().m_maxThreads; + for (uint32_t i = 0; i < maxThreads; ++i) { + if (m_profileFunctionDataArray[i] != nullptr) { + ThreadProfileFunctionDataArray& nextArray = *m_profileFunctionDataArray[i]; + uint32_t usedFunctionCount = std::min(functionCount, (uint32_t)nextArray.size()); + for (uint32_t j = 0; j < usedFunctionCount; ++j) { + globalProfileData[j].Accumulate(nextArray[j]); + } + } + } + + // dump what we just collected + MOT_LOG_TRACE("JIT Profiler raw COLLECTED data dump:"); + for (uint32_t i = 0; i < globalProfileData.size(); ++i) { + MOT_LOG_TRACE(" JIT profiler function %u raw data dump:", i); + globalProfileData[i].Dump(); + } +} + +void JitProfiler::ProcessReportData(ThreadProfileFunctionDataArray& globalProfileData) +{ + // first round: add missing meta-data + { + std::unique_lock lock(m_functionIdLock); + for (uint32_t i = 0; i < globalProfileData.size(); ++i) { + JitProfileFunctionData& profileData = globalProfileData[i]; + ProfileFunctionIdNameMap::iterator itr = m_profileFunctionIdNameMap.find(i); + if (itr != m_profileFunctionIdNameMap.end()) { + QualifiedName& qname = itr->second; + ProfileFunctionMetaDataMap::iterator itr2 = m_profileFunctionMetaDataMap.find(qname); + if (itr2 != m_profileFunctionMetaDataMap.end()) { + JitProfileFunctionMetaData* sourceData = itr2->second; + profileData.CopyMetaData(sourceData); + } + } + } + } + + // second round: accumulate child-query data into parent + for (uint32_t i = 0; i < globalProfileData.size(); ++i) { + JitProfileFunctionData& profileData = globalProfileData[i]; + const SubQueryIdArray& idArray = profileData.GetSubQueryIdArray(); + for (uint32_t j = 0; j < idArray.size(); ++j) { + uint32_t childIndex = idArray[j]; + profileData.AccumulateChild(globalProfileData[childIndex]); + } + } +} + +void JitProfiler::Cleanup() +{ + std::unique_lock lock(m_functionIdLock); + + // clean map + ProfileFunctionMetaDataMap::iterator itr = m_profileFunctionMetaDataMap.begin(); + while (itr != m_profileFunctionMetaDataMap.end()) { + delete itr->second; + ++itr; + } + m_profileFunctionMetaDataMap.clear(); + + // clean global array + for (uint32_t i = 0; i < m_profileFunctionDataArray.size(); ++i) { + if (m_profileFunctionDataArray[i] != nullptr) { + delete m_profileFunctionDataArray[i]; + } + } + m_profileFunctionDataArray.clear(); +} +} // namespace JitExec \ No newline at end of file diff --git a/src/gausskernel/storage/mot/jit_exec/jit_profiler.h b/src/gausskernel/storage/mot/jit_exec/jit_profiler.h new file mode 100644 index 000000000..afc477988 --- /dev/null +++ b/src/gausskernel/storage/mot/jit_exec/jit_profiler.h @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * ------------------------------------------------------------------------- + * + * jit_profiler.h + * JIT LLVM. + * + * IDENTIFICATION + * src/gausskernel/storage/mot/jit_exec/jit_profiler.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef JIT_PROFILER_H +#define JIT_PROFILER_H + +#include "storage/mot/jit_def.h" +#include "c.h" +#include "pgstat.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "cycles.h" + +namespace JitExec { + +/** @typedef Vector of sub-query ids. */ +using SubQueryIdArray = std::vector; + +/** @struct The meta-data for a single profiled function. */ +class JitProfileFunctionMetaData { +public: + /** @typedef Map of region name by id. */ + using ProfileRegionIdNameMap = std::map; + + /** @brief Constructor. */ + JitProfileFunctionMetaData() + : m_functionOid(InvalidOid), + m_functionId(MOT_JIT_PROFILE_INVALID_FUNCTION_ID), + m_parentFunctionId(MOT_JIT_PROFILE_INVALID_FUNCTION_ID), + m_nameSpace(MOT_JIT_GLOBAL_QUERY_NS) + {} + + JitProfileFunctionMetaData(Oid functionOid, uint32_t functionId) + : m_functionOid(functionOid), + m_functionId(functionId), + m_parentFunctionId(MOT_JIT_PROFILE_INVALID_FUNCTION_ID), + m_nameSpace(MOT_JIT_GLOBAL_QUERY_NS) + {} + + JitProfileFunctionMetaData(Oid functionOid, uint32_t functionId, int parentFunctionId, const char* nameSpace) + : m_functionOid(functionOid), + m_functionId(functionId), + m_parentFunctionId(parentFunctionId), + m_nameSpace(nameSpace) + {} + + inline Oid GetFunctionOid() const + { + return m_functionOid; + } + + inline uint32_t GetFunctionId() const + { + return m_functionId; + } + + inline uint32_t GetParentFunctionId() const + { + return m_parentFunctionId; + } + + inline const char* GetNameSpace() const + { + return m_nameSpace.c_str(); + } + + inline void AddChildQuery(uint32_t queryId) + { + m_subQueryIdArray.push_back(queryId); + } + + /** @brierf Retrieves profile region identifier by name (allocate on-demand). */ + uint32_t GetProfileRegionDataId(const char* name); + + inline uint32_t GetTotalRegionId() const + { + return FindProfileRegionId(MOT_JIT_PROFILE_REGION_TOTAL); + } + + inline uint32_t GetDefVarsRegionId() const + { + return FindProfileRegionId(MOT_JIT_PROFILE_REGION_DEF_VARS); + } + + inline uint32_t GetInitVarsRegionId() const + { + return FindProfileRegionId(MOT_JIT_PROFILE_REGION_INIT_VARS); + } + + inline uint32_t GetChildCallRegionId() const + { + return FindProfileRegionId(MOT_JIT_PROFILE_REGION_CHILD_CALL); + } + + uint32_t FindProfileRegionId(const char* name) const; + + /** @brief Get reference to sub-query id array. */ + inline const SubQueryIdArray& GetSubQueryIdArray() const + { + return m_subQueryIdArray; + } + + inline const ProfileRegionIdNameMap& GetRegionIdNameMap() const + { + return m_profileRegionIdNameMap; + } + +private: + /** @var The unique identifier of the function in pg_proc. */ + Oid m_functionOid; + + /** @var The profiled function identifier. */ + uint32_t m_functionId; + + /** @var The profile data identifier of the parent function. */ + uint32_t m_parentFunctionId; + + /** @var The name-space of the profiled function/query. */ + std::string m_nameSpace; + + /** @typedef Map of region identifier by name. */ + using ProfileRegionIdMap = std::map; + + /** @var The map of region identifier by name. */ + ProfileRegionIdMap m_profileRegionIdMap; + + /** @var The map of region name by identifier. */ + ProfileRegionIdNameMap m_profileRegionIdNameMap; + + /** @var The id array of sub-queries of this function. */ + SubQueryIdArray m_subQueryIdArray; +}; + +/** @struct The profile data for a single profile region. */ +struct JitProfileRegionData { + JitProfileRegionData() : m_accumulatedTime(0), m_nextStartTime(0), m_sampleCount(0) + {} + + /** @var Total accumulated time of all samples. */ + uint64_t m_accumulatedTime; + + /** @var Start time of next sample. */ + uint64_t m_nextStartTime; + + /** @var Total number of samples. */ + uint64_t m_sampleCount; +}; + +/** @class JitProfileFunctionData Profile data for a single function. */ +class JitProfileFunctionData { +public: + /** @brief Constructor. */ + JitProfileFunctionData() + : m_functionOid(InvalidOid), + m_functionId(MOT_JIT_PROFILE_INVALID_FUNCTION_ID), + m_parentFunctionId(MOT_JIT_PROFILE_INVALID_FUNCTION_ID), + m_nameSpace(MOT_JIT_GLOBAL_QUERY_NS), + m_childGrossTime(0), + m_childNetTime(0), + m_totalRegionId(MOT_JIT_PROFILE_INVALID_REGION_ID) + {} + + /** @brief Destructor. */ + virtual ~JitProfileFunctionData() + {} + + inline Oid GetFunctionOid() const + { + return m_functionOid; + } + + inline uint32_t GetFunctionId() const + { + return m_functionId; + } + + inline uint32_t GetParentFunctionId() const + { + return m_parentFunctionId; + } + + inline const char* GetNamespace() const + { + return m_nameSpace.c_str(); + } + + inline uint32_t GetTotalSampleCount() const + { + uint32_t sampleCount = 0; + if ((m_totalRegionId != MOT_JIT_PROFILE_INVALID_REGION_ID) && + (m_totalRegionId < m_profileRegionDataArray.size())) { + sampleCount = m_profileRegionDataArray[m_totalRegionId].m_sampleCount; + } + return sampleCount; + } + + void CopySubQueryIdArray(const SubQueryIdArray& rhs); + + /** @var Mark start time of next sample in a profile region. */ + inline void StartProfileRegionSample(uint32_t id) + { + if (id >= m_profileRegionDataArray.size()) { + m_profileRegionDataArray.resize(id + 1); + } + m_profileRegionDataArray[id].m_nextStartTime = GetSysClock(); + } + + /** @var Mark end time of next sample in a profile region. */ + inline void EndProfileRegionSample(uint32_t id) + { + uint64_t endTime = GetSysClock(); + JitProfileRegionData& pd = m_profileRegionDataArray[id]; + pd.m_accumulatedTime += (endTime - pd.m_nextStartTime); + pd.m_nextStartTime = 0; + ++pd.m_sampleCount; + } + + void CopyMetaData(JitProfileFunctionMetaData* sourceData); + + /** @brief Accumulate profile data into this object. */ + void Accumulate(const JitProfileFunctionData& rhs); + + /** @brief Accumulate profile data of child-query into this object. */ + void AccumulateChild(const JitProfileFunctionData& rhs); + + void Dump(); + + /** @brief Issue profile data report for this object. */ + void Report(const char* functionName, const JitProfileFunctionMetaData& profileMetaData); + + /** @brief Issue profile data entry for this object. */ + void Report(MotJitProfile* entry, uint32_t defVarsRegionId, uint32_t initVarsRegionId, uint32_t childCallRegionId); + + /** @brief Get reference to sub-query id array. */ + inline const SubQueryIdArray& GetSubQueryIdArray() const + { + return m_subQueryIdArray; + } + +private: + /** @var The unique identifier of the function in pg_proc. */ + Oid m_functionOid; + + /** @var The profiled function identifier. */ + uint32_t m_functionId; + + /** @var The profile data identifier of the parent function. */ + uint32_t m_parentFunctionId; + + /** @var The name-space of the profiled function/query. */ + std::string m_nameSpace; + + /** @typedef Array of profile data per region. */ + using ProfileRegionDataArray = std::vector; + + /** @var The run-time profile data. */ + ProfileRegionDataArray m_profileRegionDataArray; + + /** @var The id array of sub-queries of this function. */ + SubQueryIdArray m_subQueryIdArray; + + /** @var Accumulation of child query profile data. */ + uint64_t m_childGrossTime; + + /** @var Accumulation of child query profile data. */ + uint64_t m_childNetTime; + + /** @var The total region identifier. */ + uint32_t m_totalRegionId; +}; + +/** @class JitProfiler A run-time profiler for jitted stored procedures. */ +class JitProfiler { +public: + /** + * @brief Creates the single instance of the profiler. + * @return True if instance creation succeeded, otherwise false. + */ + static bool CreateInstance(); + + /** @brief Destroys the single instance of the profiler. */ + static void DestroyInstance(); + + /** @brief Retrieves a reference to the single instance of the profiler. */ + static JitProfiler* GetInstance(); + + /** @brief Retrieves the function identifier by name (allocate on-demand). */ + uint32_t GetProfileFunctionId(const char* functionName, Oid functionOid); + + /** @brief Retrieves the query identifier by name (allocate on-demand). */ + uint32_t GetProfileQueryId(const char* nameSpace, const char* queryString); + + /** @brief Retrieves the profile region identifier by name (allocate on-demand). */ + uint32_t GetProfileRegionId(const char* functionName, const char* regionName); + + /** @brief Retrieves the profile region identifier by name (allocate on-demand). */ + uint32_t GetQueryProfileRegionId(const char* nameSpace, const char* queryString, const char* regionName); + + /** @brief Emit run-time profile data. */ + void EmitProfileData(uint32_t functionId, uint32_t regionId, bool startEvent); + + /** @brief Generate profile view report. */ + MotJitProfile* GetProfileReport(uint32_t* num); + +private: + /** @typedef Map of function/region identifiers by name. */ + using QualifiedName = std::pair; + using ProfileFunctionMetaDataMap = std::map; + using ProfileFunctionIdNameMap = std::map; + + /** @typedef Per-thread array of function profile data (entry per-function). */ + using ThreadProfileFunctionDataArray = std::vector; + + /** @typedef Global array of function profile data (entry per-thread). */ + using GlobalProfileFunctionDataArray = std::vector; + + /** @brief Constructor. */ + JitProfiler(); + + /** @brief Destructor. */ + ~JitProfiler(); + + /** @brief Terminate all worker threads. */ + void GenerateReport(); + + void DumpRawData(); + + /** @brief Collect profile data. */ + void CollectReportData(ThreadProfileFunctionDataArray& globalProfileData); + + /** @brief Process profile data. */ + void ProcessReportData(ThreadProfileFunctionDataArray& globalProfileData); + + /** @brief Release all resource. */ + void Cleanup(); + + /** @var The singleton instance of the thread pool. */ + static JitProfiler* m_instance; + + /** @var Function identifier map. */ + ProfileFunctionMetaDataMap m_profileFunctionMetaDataMap; + + /** @var Function identifier to name map. */ + ProfileFunctionIdNameMap m_profileFunctionIdNameMap; + + /** @var Global profile data array for all threads. */ + GlobalProfileFunctionDataArray m_profileFunctionDataArray; + + /** @var Lock to synchronize thread pool access. */ + std::mutex m_functionIdLock; + + /** @var The profiled function counter. */ + uint32_t m_nextFunctionId; +}; +} // namespace JitExec + +#endif /* JIT_PROFILER_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source.cpp b/src/gausskernel/storage/mot/jit_exec/jit_source.cpp index 2badbf3c8..ca725e31d 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_source.cpp @@ -29,179 +29,1308 @@ #include "jit_statistics.h" #include "debug_utils.h" #include "mm_global_api.h" +#include "jit_common.h" +#include "jit_plan.h" +#include "storage/mot/jit_exec.h" +#include "jit_llvm_query_codegen.h" +#include "jit_plan_sp.h" +#include "jit_llvm_sp_codegen.h" +#include "jit_source_pool.h" +#include "jit_source_map.h" +#include "mot_list.h" + +#include "executor/spi.h" namespace JitExec { DECLARE_LOGGER(JitSource, JitExec); // forward declarations -static char* CloneQueryString(const char* queryString); -static void FreeQueryString(char* queryString); -static char* ReallocQueryString(char* oldQueryString, const char* newQueryString); -static void SetJitSourceStatus(JitSource* jitSource, JitContext* readySourceJitContext, JitContextStatus status, - bool notifyAll = true, int errorCode = 0, uint64_t relationId = 0, bool* invalidState = nullptr); -static const char* JitContextStatusToString(JitContextStatus status); +static char* CloneQueryString(const char* queryString, JitContextUsage usage); +static void FreeQueryString(char* queryString, JitContextUsage usage); +static void SetJitSourceStatus(JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenState state, + JitCodegenStats* codegenStats = nullptr, int errorCode = 0, uint64_t relationId = 0, bool* invalidState = nullptr, + bool functionReplace = false, JitCodegenState* newState = nullptr); static void JitSourcePurgeContextList(JitSource* jitSource, uint64_t relationId); -static void RemoveJitSourceContextImpl(JitSource* jitSource, JitContext* jitContext); +static void JitSourceInformCompileDone(JitSource* jitSource, JitSourceList* parentSourceList = nullptr); +static void JitSourceInformCompileError(JitSource* jitSource, JitSourceList* parentSourceList = nullptr); +static void JitSourceListInformCompileDone(JitSourceList* sourceList, const char* queryString); +static void JitSourceListInformCompileError(JitSourceList* sourceList, const char* queryString); +static void JitSourceInvalidateContextList(JitSource* jitSource, uint64_t relationId); +static void JitSourceDeprecateContextList(JitSource* jitSource); +static void RemoveJitSourceContextImpl(JitSource* jitSource, MotJitContext* jitContext); +static bool JitQueryContextRefersSP(JitQueryContext* jitContext, Oid functionId); +static bool JitFunctionContextRefersSP(JitFunctionContext* jitContext, Oid functionId); +static Query* GetSourceQuery(const char* queryString); +static bool JitCodegenQueryInplace(Query* query, const char* queryString, JitPlan* jitPlan, JitSource* jitSource); +static JitCodegenState RegenerateQuery(JitSource* jitSource); +static JitCodegenState ReinstateReadyState(JitSource* jitSource); +static JitCodegenState RevalidateJitInvokeQuery(JitSource* jitSource); +static bool RevalidateJitFunction(JitSource* jitSource); +static JitCodegenState RevalidateJitQuerySource(JitSource* jitSource, JitCodegenState prevState); +static JitCodegenState RevalidateJitFunctionSource(JitSource* jitSource, JitCodegenState prevState); +static bool CopyJitSource( + JitSource* source, JitSource* target, bool cloneContext, JitContextUsage usage, JitSourceOp sourceOp); +static void DeprecateSourceJitContext(JitSource* jitSource, MotJitContext** recyclableContext); +static void UnlinkDeprecateJitContext(JitSource* jitSource, MotJitContext* sourceJitContext); #ifdef MOT_DEBUG -static void VerifyJitSourceStateTrans(JitSource* jitSource, JitContext* readySourceJitContext, JitContextStatus status); -#define MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, status) \ - VerifyJitSourceStateTrans(jitSource, readySourceJitContext, status) +static bool DeprecateListContainsJitContext(JitSource* jitSource, MotJitContext* jitContext); +static bool ContextListContainsJitContext(JitSource* jitSource, MotJitContext* jitContext); +static void VerifyJitSourceStateTrans( + JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenState state); +#define MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, state) \ + VerifyJitSourceStateTrans(jitSource, readySourceJitContext, state) #else -#define MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, status) +#define MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, state) #endif -extern bool InitJitSource(JitSource* jitSource, const char* queryString) +static TimestampTz GetCurrentTimestamp() { - jitSource->_query_string = NULL; - jitSource->_source_jit_context = NULL; - jitSource->_initialized = 0; + return OidFunctionCall0Coll(2649, InvalidOid); // clock_timestamp +} - jitSource->_status = JIT_CONTEXT_UNAVAILABLE; - jitSource->_next = NULL; +extern bool InitJitSource(JitSource* jitSource, const char* queryString, JitContextUsage usage) +{ + jitSource->m_queryString = nullptr; + jitSource->m_sourceJitContext = nullptr; + jitSource->m_initialized = 0; + + jitSource->m_pendingChildCompile = 0; + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + jitSource->m_status = JitSourceStatus::JIT_SOURCE_INVALID; + jitSource->m_contextType = JitContextType::JIT_CONTEXT_TYPE_INVALID; + jitSource->m_commandType = JIT_COMMAND_INVALID; + jitSource->m_usage = usage; + jitSource->m_deprecatedPendingCompile = 0; + jitSource->m_timestamp = GetCurrentTimestamp(); + jitSource->m_next = nullptr; jitSource->m_contextList = nullptr; + jitSource->m_functionOid = InvalidOid; + jitSource->m_functionTxnId = 0; + jitSource->m_expireTxnId = 0; + jitSource->m_tableId = 0; + jitSource->m_innerTableId = 0; + jitSource->m_deprecateContextList = nullptr; + jitSource->m_codegenStats = {}; - int res = pthread_mutex_init(&jitSource->_lock, NULL); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE( - res, pthread_mutex_init, "Code Generation", "Failed to create mutex for jit-source"); + if (queryString == nullptr) { + jitSource->m_queryString = nullptr; } else { - res = pthread_cond_init(&jitSource->_cond, NULL); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE( - res, pthread_cond_init, "Code Generation", "Failed to create mutex for jit-source"); - pthread_mutex_destroy(&jitSource->_lock); - } else { - jitSource->_query_string = CloneQueryString(queryString); - jitSource->_initialized = 1; + jitSource->m_queryString = CloneQueryString(queryString, usage); + if (jitSource->m_queryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Code Generation", "Failed to clone query string"); + return false; } } - return jitSource->_initialized != 0 ? true : false; + // we need a recursive mutex, due to complexities when registering context for cleanup (see call to + // CloneJitContext() inside WaitJitContextReady()) + // note: even though a local JIT source does not need a lock, we still create it because the local source is used + // in many functions requiring a lock and condition variable + pthread_mutexattr_t attr; + int res = pthread_mutexattr_init(&attr); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE( + res, pthread_mutex_init, "Code Generation", "Failed to init mutex attributes for jit-source"); + FreeQueryString(jitSource->m_queryString, usage); + jitSource->m_queryString = nullptr; + return false; + } + (void)pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + res = pthread_mutex_init(&jitSource->m_lock, &attr); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE( + res, pthread_mutex_init, "Code Generation", "Failed to create mutex for jit-source"); + FreeQueryString(jitSource->m_queryString, usage); + jitSource->m_queryString = nullptr; + return false; + } + + jitSource->m_initialized = 1; + return true; } -extern void DestroyJitSource(JitSource* jitSource) +static bool CopyJitSource( + JitSource* source, JitSource* target, bool cloneContext, JitContextUsage usage, JitSourceOp sourceOp) { - MOT_LOG_TRACE("Destroying JIT source %p with query string: %s", jitSource, jitSource->_query_string); + // clone the source context if required + target->m_sourceJitContext = nullptr; + if (cloneContext && (source->m_sourceJitContext != nullptr)) { + target->m_sourceJitContext = CloneJitContext(source->m_sourceJitContext, usage); + if (target->m_sourceJitContext == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Process DDL event", "Failed to clone source JIT context"); + return false; + } + RemoveJitSourceContext(source, target->m_sourceJitContext); + target->m_sourceJitContext->m_jitSource = target; + PurgeJitContext(target->m_sourceJitContext, 0); // ensure we work with correct table/index objects + } + + // clone the query string + target->m_queryString = CloneQueryString(source->m_queryString, usage); + if (target->m_queryString == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Process DDL event", "Failed to clone query string"); + return false; + } + + // clone the rest of the members + target->m_commandType = source->m_commandType; + if (cloneContext) { + target->m_codegenState = source->m_codegenState; + } else { + if (sourceOp == JitSourceOp::JIT_SOURCE_PURGE_ONLY) { + target->m_codegenState = source->m_codegenState; + } else if (sourceOp == JitSourceOp::JIT_SOURCE_INVALIDATE) { + target->m_codegenState = JitCodegenState::JIT_CODEGEN_EXPIRED; + target->m_status = JitSourceStatus::JIT_SOURCE_INVALID; // move to transient state + } else if (sourceOp == JitSourceOp::JIT_SOURCE_REPLACE_FUNCTION) { + target->m_codegenState = JitCodegenState::JIT_CODEGEN_REPLACED; + target->m_status = JitSourceStatus::JIT_SOURCE_INVALID; // move to transient state + } else if (sourceOp == JitSourceOp::JIT_SOURCE_DROP_FUNCTION) { + target->m_codegenState = JitCodegenState::JIT_CODEGEN_DROPPED; + } + } + + target->m_contextType = source->m_contextType; + target->m_timestamp = GetCurrentTimestamp(); + target->m_functionOid = source->m_functionOid; + target->m_functionTxnId = source->m_functionTxnId; + target->m_expireTxnId = source->m_expireTxnId; + + // move current session local contexts to target + MoveCurrentSessionContexts(source, target, sourceOp); + return true; +} + +static void CleanJitSource(JitSource* jitSource) +{ + MOT_LOG_DEBUG("Cleaning JIT source %p with query string: %s", + jitSource, + (jitSource->m_queryString != nullptr) ? jitSource->m_queryString : "NULL"); + if (jitSource->m_contextList != nullptr) { - MOT_LOG_WARN("JIT source still contains registered JIT context objects, while destroying JIT source"); - JitContext* jitContext = jitSource->m_contextList; + MOT_LOG_WARN("JIT source %p still contains registered JIT context objects %p, while cleaning JIT source with " + "query string: %s", + jitSource, + jitSource->m_contextList, + (jitSource->m_queryString != nullptr) ? jitSource->m_queryString : "NULL"); + MotJitContext* jitContext = jitSource->m_contextList; while (jitContext != nullptr) { + MOT_LOG_DEBUG("JIT context %p still found in JIT source %p", jitContext, jitSource); jitContext->m_jitSource = nullptr; // prevent crash during DestroyJitContext() jitContext = jitContext->m_nextInSource; } jitSource->m_contextList = nullptr; } - if (jitSource->_source_jit_context != nullptr) { + if (jitSource->m_deprecateContextList != nullptr) { + MOT_LOG_WARN("JIT source %p still contains deprecate JIT context objects %p, while cleaning JIT source with " + "query string: %s", + jitSource, + jitSource->m_deprecateContextList, + (jitSource->m_queryString != nullptr) ? jitSource->m_queryString : "NULL"); + MotJitContext* jitContext = jitSource->m_deprecateContextList; + while (jitContext != nullptr) { + MOT_LOG_DEBUG("Deprecate JIT context %p still found in JIT source %p", jitContext, jitSource); + jitContext->m_jitSource = nullptr; // prevent crash during DestroyJitContext() + jitContext = jitContext->m_nextDeprecate; + } + jitSource->m_deprecateContextList = nullptr; + } + + if (jitSource->m_sourceJitContext != nullptr) { // this code is executed during shutdown, so the underlying // GsCodeGen object was already destroyed by the top memory context - DestroyJitContext(jitSource->_source_jit_context); - jitSource->_source_jit_context = NULL; + jitSource->m_sourceJitContext->m_jitSource = nullptr; // suppress warning + DestroyJitContext(jitSource->m_sourceJitContext); + jitSource->m_sourceJitContext = nullptr; } - if (jitSource->_query_string != nullptr) { - FreeQueryString(jitSource->_query_string); - jitSource->_query_string = NULL; - } - if (jitSource->_initialized) { - pthread_cond_destroy(&jitSource->_cond); - pthread_mutex_destroy(&jitSource->_lock); - jitSource->_initialized = 0; + if (jitSource->m_queryString != nullptr) { + FreeQueryString(jitSource->m_queryString, jitSource->m_usage); + jitSource->m_queryString = nullptr; } } -extern void ReInitJitSource(JitSource* jitSource, const char* queryString) +extern void DestroyJitSource(JitSource* jitSource) { - jitSource->_query_string = ReallocQueryString(jitSource->_query_string, queryString); - jitSource->_source_jit_context = NULL; - jitSource->_status = JIT_CONTEXT_UNAVAILABLE; - jitSource->_next = NULL; -} + MOT_LOG_DEBUG("Destroying JIT source %p with query string: %s", + jitSource, + (jitSource->m_queryString != nullptr) ? jitSource->m_queryString : "NULL"); -extern JitContextStatus WaitJitContextReady(JitSource* jitSource, JitContext** readySourceJitContext) -{ - MOT_LOG_TRACE("%p: Waiting for jit-source to become ready on query: %s", jitSource, jitSource->_query_string); - - *readySourceJitContext = NULL; - - pthread_mutex_lock(&jitSource->_lock); - - while ((jitSource->_status == JIT_CONTEXT_UNAVAILABLE) && (jitSource->_source_jit_context == NULL)) { - pthread_cond_wait(&jitSource->_cond, &jitSource->_lock); + CleanJitSource(jitSource); + if (jitSource->m_initialized) { + (void)pthread_mutex_destroy(&jitSource->m_lock); + jitSource->m_initialized = 0; } - JitContextStatus result = jitSource->_status; - if (jitSource->_status == JIT_CONTEXT_EXPIRED) { - // special case: we return to caller status-expired and change internal status to unavailable - jitSource->_status = JIT_CONTEXT_UNAVAILABLE; - } else if (jitSource->_status == JIT_CONTEXT_READY) { - // we need to re-fetch index objects in case TRUNCATE TABLE was issued - if ((jitSource->_source_jit_context->m_index == nullptr) && - (jitSource->_source_jit_context->m_commandType != JIT_COMMAND_INSERT)) { - if (!ReFetchIndices(jitSource->_source_jit_context)) { - MOT_LOG_TRACE("Failed to re-fetch index objects for source %p with query: %s", - jitSource, - jitSource->_query_string); - result = JIT_CONTEXT_ERROR; - } + FreeJitSource(jitSource); +} + +extern void ReInitJitSource(JitSource* jitSource, const char* queryString, JitContextUsage usage) +{ + jitSource->m_usage = usage; + jitSource->m_queryString = CloneQueryString(queryString, usage); + jitSource->m_sourceJitContext = NULL; + jitSource->m_commandType = JIT_COMMAND_INVALID; + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + jitSource->m_status = JitSourceStatus::JIT_SOURCE_INVALID; + jitSource->m_contextType = JitContextType::JIT_CONTEXT_TYPE_INVALID; + jitSource->m_timestamp = GetCurrentTimestamp(); + jitSource->m_next = nullptr; + jitSource->m_pendingChildCompile = 0; + jitSource->m_deprecatedPendingCompile = 0; + jitSource->m_functionOid = 0; + jitSource->m_functionTxnId = 0; + jitSource->m_expireTxnId = 0; + jitSource->m_tableId = 0; + jitSource->m_innerTableId = 0; + jitSource->m_deprecateContextList = nullptr; + jitSource->m_codegenStats = {}; +} + +extern void LockJitSource(JitSource* jitSource) +{ + (void)pthread_mutex_lock(&jitSource->m_lock); +} + +extern void UnlockJitSource(JitSource* jitSource) +{ + (void)pthread_mutex_unlock(&jitSource->m_lock); +} + +static inline bool IsInvalidatedCodegenState(JitCodegenState codegenState) +{ + if ((codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) || + (codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) || + (codegenState == JitCodegenState::JIT_CODEGEN_REPLACED) || + (codegenState == JitCodegenState::JIT_CODEGEN_DEPRECATE)) { + return true; + } + return false; +} + +extern bool IsPrematureRevalidation(JitSource* jitSource, TransactionId functionTxnId) +{ + (void)pthread_mutex_lock(&jitSource->m_lock); + MOT_ASSERT((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) || + ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType == JIT_COMMAND_INVOKE))); + if ((jitSource->m_functionTxnId > functionTxnId) || (jitSource->m_expireTxnId > functionTxnId) || + ((jitSource->m_functionTxnId == functionTxnId) && (jitSource->m_expireTxnId != InvalidTransactionId) && + IsInvalidatedCodegenState(jitSource->m_codegenState))) { + MOT_LOG_TRACE("Rejecting premature re-validation attempt (Function %u TxnId from %" PRIu64 "/%" PRIu64 + " to %" PRIu64 ") of JIT source %p with state %s for query: %s", + jitSource->m_functionOid, + jitSource->m_functionTxnId, + jitSource->m_expireTxnId, + functionTxnId, + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return true; + } + + MOT_LOG_TRACE("Valid re-validation attempt (Function %u TxnId from %" PRIu64 "/%" PRIu64 " to %" PRIu64 ") of JIT " + "source %p with state %s for query: %s", + jitSource->m_functionOid, + jitSource->m_functionTxnId, + jitSource->m_expireTxnId, + functionTxnId, + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return false; +} + +extern bool IsPrematureRevalidation(JitSource* jitSource, JitPlan* jitPlan) +{ + if (jitPlan == MOT_READY_JIT_PLAN) { + MOT_LOG_TRACE("Rejecting possible premature re-validation attempt (Function %u TxnId %" PRIu64 "/%" PRIu64 ") " + "of JIT %p with state %s for query: %s", + jitSource->m_functionOid, + jitSource->m_functionTxnId, + jitSource->m_expireTxnId, + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + return true; + } + + JitFunctionPlan* functionPlan = nullptr; + if (JitPlanHasFunctionPlan(jitPlan, &functionPlan)) { + if (IsPrematureRevalidation(jitSource, functionPlan->m_function->fn_xmin)) { + return true; } - if (result != JIT_CONTEXT_ERROR) { - *readySourceJitContext = CloneJitContext(jitSource->_source_jit_context); + } + + return false; +} + +static TransactionId GetFunctionTxnIdFromPlan(JitPlan* jitPlan) +{ + TransactionId functionTxnId = InvalidTransactionId; + if (jitPlan != MOT_READY_JIT_PLAN) { + JitFunctionPlan* functionPlan = nullptr; + if (JitPlanHasFunctionPlan(jitPlan, &functionPlan)) { + functionTxnId = functionPlan->m_function->fn_xmin; + } + } + return functionTxnId; +} + +extern JitCodegenState GetReadyJitContext( + JitSource* jitSource, MotJitContext** readySourceJitContext, JitContextUsage usage, JitPlan* jitPlan) +{ + MOT_LOG_TRACE( + "Trying to get ready JIT context from JIT source %p for query: %s", jitSource, jitSource->m_queryString); + + *readySourceJitContext = nullptr; + + (void)pthread_mutex_lock(&jitSource->m_lock); + + // If source was deprecated we abort + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DEPRECATE) { + (void)pthread_mutex_unlock(&jitSource->m_lock); + MOT_LOG_TRACE("Aborting codegen in deprecate JIT source %p: %s", jitSource, jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_DEPRECATE; + } + + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) { + (void)pthread_mutex_unlock(&jitSource->m_lock); + MOT_LOG_TRACE("Aborting codegen in dropped JIT source %p: %s", jitSource, jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_DROPPED; + } + + // If someone else is operating on the source then we just inform the caller + // that the code is being generated by returning JitCodegenState::JIT_CODEGEN_UNAVAILABLE. + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + MOT_LOG_TRACE("Backing-off in unavailable JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + } + + // if the source was previously marked as pending for child compilation to finish, then check again + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_PENDING) { + if (jitSource->m_pendingChildCompile == JIT_CONTEXT_PENDING_COMPILE) { + MOT_LOG_TRACE("Backing-off in pending JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_PENDING; + } else { + // whether done or error, just reset and retry + MOT_LOG_TRACE("Child compile done/error in pending JIT source %p: %s", jitSource, jitSource->m_queryString); + // call to revalidate below should eventually reset the pending flag + } + } + + JitCodegenState result = jitSource->m_codegenState; + if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) || + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_REPLACED) || + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_ERROR)) { + if (IsPrematureRevalidation(jitSource, jitPlan)) { + MOT_LOG_TRACE("JIT source %p is %s, skipping premature re-validation: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + // Premature re-validation attempt: We don't change the codegen state. + // We return to caller status-deprecate, this way the caller will abort this codegen attempt. + result = JitCodegenState::JIT_CODEGEN_DEPRECATE; + } else { + // Special case: We return to caller status-expired and change internal status to unavailable, this way the + // caller will regenerate code for the query. + MOT_LOG_TRACE("JIT source %p moving from state %s to UNAVAILABLE/EXPIRED: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + result = JitCodegenState::JIT_CODEGEN_EXPIRED; + } + } else if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) || + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_PENDING)) { + // check if we need to revalidate the source context, this can happen with INVOKE context (all query context + // source objects are destroyed when their source expires). + // The relevant use case here is PREPARE after invalidate. + MOT_LOG_TRACE("JIT source %p is ready: %s", jitSource, jitSource->m_queryString); + bool needRevalidate = ((MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState) != JIT_CONTEXT_VALID) || + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_PENDING)); + if (needRevalidate) { + // we must unlock in order to avoid deadlock, since revalidation may take source map lock when some SP or + // sub-query needs full code regeneration (so IsJittableFunction/Query() calls + // ContainsReadyCachedJitSource() which locks the source map). + MOT_LOG_TRACE("JIT source %p needs revalidation: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + + TransactionId functionTxnId = GetFunctionTxnIdFromPlan(jitPlan); + + // this is ugly in terms of dependencies, but we must revalidate in a transactional manner + result = RevalidateJitSourceTxn(jitSource, functionTxnId); + + (void)pthread_mutex_lock(&jitSource->m_lock); + result = jitSource->m_codegenState; // get value again after re-lock due to possible concurrent deprecate + // We should not try to regenerate code again in this attempt, even if we failed to revalidate. + MOT_ASSERT(result != JitCodegenState::JIT_CODEGEN_EXPIRED); + } + + // we need to re-fetch table and index objects in case TRUNCATE TABLE was issued + if ((result == JitCodegenState::JIT_CODEGEN_READY) && !RefetchTablesAndIndices(jitSource->m_sourceJitContext)) { + MOT_LOG_TRACE( + "Failed to re-fetch index objects for source %p with query: %s", jitSource, jitSource->m_queryString); + SetJitSourceError(jitSource, MOT_ERROR_CONCURRENT_MODIFICATION, &result); + } + if (result == JitCodegenState::JIT_CODEGEN_READY) { + // attention: following call also makes a call to AddJitSourceContext(), which takes lock again + *readySourceJitContext = CloneJitContext(jitSource->m_sourceJitContext, usage); if (*readySourceJitContext != nullptr) { - ++u_sess->mot_cxt.jit_context_count; - (*readySourceJitContext)->m_nextInSource = jitSource->m_contextList; - jitSource->m_contextList = *readySourceJitContext; - (*readySourceJitContext)->m_jitSource = jitSource; MOT_LOG_TRACE( "Registered JIT context %p in JIT source %p for cleanup", *readySourceJitContext, jitSource); JitStatisticsProvider::GetInstance().AddCodeCloneQuery(); } else { - MOT_LOG_TRACE("Failed to clone ready source context %p", jitSource->_source_jit_context); + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Generate JIT Code", + "Failed to clone ready source context %p", + jitSource->m_sourceJitContext); JitStatisticsProvider::GetInstance().AddCodeCloneErrorQuery(); } } } - pthread_mutex_unlock(&jitSource->_lock); - MOT_LOG_TRACE("%p: Found ready context %p with query %s (result: %s, status: %s, source context: %p)", + MOT_LOG_TRACE("JIT source %p: Found %s context %p with query %s (result: %s, status: %s, source context: %p)", jitSource, + JitCodegenStateToString(result), *readySourceJitContext, - jitSource->_query_string, - JitContextStatusToString(result), - JitContextStatusToString(jitSource->_status), - jitSource->_source_jit_context); + jitSource->m_queryString, + JitCodegenStateToString(result), + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_sourceJitContext); + + (void)pthread_mutex_unlock(&jitSource->m_lock); + return result; } -extern void SetJitSourceError(JitSource* jitSource, int errorCode) +extern bool IsJitSourceReady(JitSource* jitSource) { - SetJitSourceStatus(jitSource, NULL, JIT_CONTEXT_ERROR, true, errorCode); + (void)pthread_mutex_lock(&jitSource->m_lock); + bool isReady = (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return isReady; } -extern void SetJitSourceExpired(JitSource* jitSource, uint64_t relationId) +extern int GetJitSourceValidState(JitSource* jitSource) { - SetJitSourceStatus(jitSource, NULL, JIT_CONTEXT_EXPIRED, false, 0, relationId); + int validState = JIT_CONTEXT_INVALID; + (void)pthread_mutex_lock(&jitSource->m_lock); + if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) && + (jitSource->m_sourceJitContext != nullptr)) { + validState = MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState); + } + (void)pthread_mutex_unlock(&jitSource->m_lock); + return validState; +} + +static Query* GetSourceQuery(const char* queryString) +{ + // we make a best effort to get the query + Query* query = (Query*)u_sess->mot_cxt.jit_pg_query; + if (query != nullptr) { + MOT_LOG_TRACE("Retrieved saved query"); + return query; + } + + // get query from cached plan (it may be invalid so we need to parse ourselves) + CachedPlanSource* psrc = u_sess->pcache_cxt.first_saved_plan; + List* parseTreeList = nullptr; + while (psrc != nullptr) { + if (strcmp(psrc->query_string, queryString) == 0) { + if (psrc->is_valid) { + parseTreeList = psrc->query_list; + break; + } + // there might be another valid plan? + } + psrc = psrc->next_saved; + } + + // extract query from parse tree list + if (parseTreeList != nullptr) { + int queryCount = list_length(parseTreeList); + if (queryCount == 1) { + query = (Query*)linitial(parseTreeList); + MOT_LOG_TRACE("Retrieved query from plan source"); + return query; + } else { + MOT_LOG_TRACE( + "Cannot get query: Invalid query list size %d in plan source for query: %s", queryCount, queryString); + return nullptr; + } + } + + MOT_LOG_TRACE("Cannot get query: Could not find plan source for query: %s", queryString); + return nullptr; +} + +static bool JitCodegenQueryInplace(Query* query, const char* queryString, JitPlan* jitPlan, JitSource* jitSource) +{ + MotJitContext* sourceJitContext = nullptr; + JitCodegenStats codegenStats = {}; + + uint64_t startTime = GetSysClock(); + MOT_LOG_TRACE("Generating LLVM JIT context for query: %s", queryString); + sourceJitContext = JitCodegenLlvmQuery(query, queryString, jitPlan, codegenStats); + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_codegenTime = timeMicros; + + if (sourceJitContext == nullptr) { + // notify error for all waiters - this query will never again be JITTed - cleanup only during database shutdown + MOT_LOG_TRACE("Failed to generate code for query with JIT source %p, signaling error context for query: %s", + jitSource, + queryString); + SetJitSourceError(jitSource, MOT::GetRootError()); + JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); + return false; + } + + MOT_LOG_TRACE("Generated JIT context %p for query: %s", sourceJitContext, queryString); + + JitCodegenState newState = JitCodegenState::JIT_CODEGEN_NONE; + if (!SetJitSourceReady(jitSource, sourceJitContext, &codegenStats, &newState)) { + // this is illegal state transition error in JIT source (internal bug) + // there is already a JIT context present in the JIT source (maybe generated by another session, + // although impossible), so we just fail JIT for this session (other sessions may still benefit from + // existing JIT context). Pay attention that we cannot replace the JIT context in the JIT source, since + // the JIT function in it is probably still being used by other sessions + MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, + "JIT Compile", + "Failed to set ready source context %p to JIT source %p (newState %s), disqualifying query: %s", + sourceJitContext, + jitSource, + JitCodegenStateToString(newState), + queryString); + DestroyJitContext(sourceJitContext); + JitStatisticsProvider::GetInstance().AddCodeGenErrorQuery(); + return false; + } + MOT_LOG_TRACE("Installed ready JIT context %p for query: %s", sourceJitContext, queryString); + + // update statistics + JitStatisticsProvider& instance = JitStatisticsProvider::GetInstance(); + instance.AddCodeGenTime(MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime)); + instance.AddCodeGenQuery(); + instance.AddCodeCloneQuery(); + return true; +} + +static JitCodegenState RegenerateQuery(JitSource* jitSource) +{ + MOT_LOG_TRACE("Regenerating JIT query source %p with query: %s", jitSource, jitSource->m_queryString); + + // get the query object + Query* query = GetSourceQuery(jitSource->m_queryString); + if (query == nullptr) { + MOT_LOG_TRACE("Cannot get source query for query text: %s", jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + // prepare a JIT plan + JitPlan* jitPlan = IsJittableQuery(query, jitSource->m_queryString, true); + if (jitPlan == nullptr) { + MOT_LOG_TRACE("Query is unjittable: %s", jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + // generate code from plan + bool codeGenResult = JitCodegenQueryInplace(query, jitSource->m_queryString, jitPlan, jitSource); + + // cleanup plan before returning + if ((jitPlan != nullptr) && (jitPlan != MOT_READY_JIT_PLAN)) { + JitDestroyPlan(jitPlan); + } + + JitCodegenState result = JitCodegenState::JIT_CODEGEN_READY; + if (!codeGenResult) { + MOT_LOG_TRACE("Code generation failed for JIT query source %p query: %s", jitSource, jitSource->m_queryString); + result = JitCodegenState::JIT_CODEGEN_ERROR; + } else { + // we need to check if there is dummy invoke context, meaning we need to back off and wait for compilation + LockJitSource(jitSource); // lock before accessing source JIT context + MOT_LOG_TRACE("Regenerated code for JIT query source %p (state %s) with query: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + result = jitSource->m_codegenState; + JitQueryContext* queryContext = (JitQueryContext*)jitSource->m_sourceJitContext; + if ((jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_DEPRECATE) && (queryContext != nullptr) && + (queryContext->m_commandType == JIT_COMMAND_INVOKE) && (queryContext->m_invokeContext != nullptr)) { + if (IsJitContextPendingCompile(queryContext->m_invokeContext)) { + MOT_LOG_TRACE("Invoked context is pending compile"); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_PENDING; + // a race condition is possible with the session that compiles the common SP, so be careful + if (jitSource->m_pendingChildCompile == 0) { + jitSource->m_pendingChildCompile = JIT_CONTEXT_PENDING_COMPILE; + } + result = JitCodegenState::JIT_CODEGEN_PENDING; + } + } + UnlockJitSource(jitSource); + } + return result; +} + +/* + * NOTE: If the source context can be recycled, it is returned in the output parameter (recyclableContext). + * Caller's responsibility to free recyclableContext if it is not NULL, since it should be done outside the + * JIT source lock. Otherwise, it could lead to deadlock when in RemoveJitSourceContext() when trying to + * ScheduleDeprecateJitSourceCleanUp(). + */ +static void DeprecatePendingCompileSource(JitSource* jitSource, MotJitContext** recyclableContext) +{ + *recyclableContext = nullptr; + if (jitSource->m_deprecatedPendingCompile) { + MOT_LOG_ERROR("JIT source %p already deprecated concurrently, changing status from %s to %s, query: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + JitCodegenStateToString(JitCodegenState::JIT_CODEGEN_DEPRECATE), + jitSource->m_queryString); + + // JIT source was deprecated by another session when we were compiling. + // Allow the JIT source to be cleaned from deprecated list now. + MOT_ASSERT(jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE); + MOT_ASSERT(jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + + // It is possible that new dummy contexts were attached after the m_deprecatedPendingCompile flag was raised. + // So we try to deprecate the context list again. + if (jitSource->m_sourceJitContext != nullptr) { + jitSource->m_sourceJitContext->m_jitSource = nullptr; // avoid warning + DeprecateSourceJitContext(jitSource, recyclableContext); + } + JitSourceDeprecateContextList(jitSource); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_DEPRECATE; + jitSource->m_deprecatedPendingCompile = 0; + jitSource->m_timestamp = GetCurrentTimestamp(); + (void)CleanUpDeprecateJitSourceContexts(jitSource); + } +} + +static JitCodegenState ReinstateReadyState(JitSource* jitSource) +{ + MotJitContext* recyclableContext = nullptr; + (void)pthread_mutex_lock(&jitSource->m_lock); + if (jitSource->m_deprecatedPendingCompile) { + // JIT source was deprecated by another session when we were compiling. + // Allow the JIT source to be cleaned from deprecated list now. + DeprecatePendingCompileSource(jitSource, &recyclableContext); + } else if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_DEPRECATE) { + MOT_ATOMIC_STORE(jitSource->m_sourceJitContext->m_validState, JIT_CONTEXT_VALID); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_READY; + jitSource->m_timestamp = GetCurrentTimestamp(); + } + JitCodegenState result = jitSource->m_codegenState; + (void)pthread_mutex_unlock(&jitSource->m_lock); + + if (recyclableContext != nullptr) { + DestroyJitContext(recyclableContext); + recyclableContext = nullptr; + } + + return result; +} + +static JitCodegenState RevalidateJitInvokeQuery(JitSource* jitSource) +{ + MOT_LOG_TRACE("Re-validating JIT INVOKE query source %p with query: %s", jitSource, jitSource->m_queryString); + + JitQueryContext* queryContext = (JitQueryContext*)jitSource->m_sourceJitContext; + uint8_t invokeValidState = MOT_ATOMIC_LOAD(queryContext->m_invokeContext->m_validState); + + // try to re-validate the underlying function context + // case 1: the underlying function context itself is invalid and needs to be re-validated (only if function was + // recreated already) + // case 2: the underlying function context itself is still valid, and some sub-query/SP needs to be re-validated + bool attemptRegen = false; + MotJitContext* invokedContext = queryContext->m_invokeContext; + if (!RevalidateJitContext(invokedContext)) { + // revalidate might fail due to backing-off while other session generates code for the same invoked SP (through + // a different invoking query) + bool isPending = false; + bool isDone = false; + bool isError = false; + if (GetJitContextCompileState(invokedContext, &isPending, &isDone, &isError)) { + if (isPending) { + // invoked SP is being compiled by another session (using a different invoking query), so we back-off + // and let this query use PG until SP compilation finishes + MOT_LOG_TRACE("Invoked context pending concurrent code generation: %s", jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + } + if (isError) { + MOT_LOG_TRACE("Failed to regenerate code for invoke code, Concurrent code generation failed: %s", + jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + if (isDone) { // very rare but might happen due to race + MOT_LOG_TRACE("Invoked context concurrent code generation done, attempting revalidation: %s", + jitSource->m_queryString); + if (!RevalidateJitContext(invokedContext)) { + MOT_LOG_TRACE("Revalidation after compile-done failed: %s:", jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + } + } else { // no concurrent compilation activity detected + // in case of dropped and recreated function we would fail here, as the function id is obsolete, so we + // attempt to regenerate the entire context tree + MOT_LOG_TRACE("Failed to revalidate invoked context, attempting full code regeneration: %s", + invokedContext->m_queryString); + attemptRegen = true; + } + } else { + // after successful revalidation we need to update the id of the invoked function + LockJitSource(jitSource); + if (jitSource->m_sourceJitContext != nullptr) { + jitSource->m_functionOid = + ((JitQueryContext*)jitSource->m_sourceJitContext)->m_invokeContext->m_functionOid; + jitSource->m_functionTxnId = + ((JitQueryContext*)jitSource->m_sourceJitContext)->m_invokeContext->m_functionTxnId; + jitSource->m_expireTxnId = 0; + } + UnlockJitSource(jitSource); + } + + // if the function itself was previously invalidated then we should also regenerate code for the invoke query, + // since signature, default parameters, etc. may have changed. + if (!attemptRegen && (invokeValidState & JIT_CONTEXT_INVALID)) { + MOT_LOG_TRACE( + "Invoked function %s context is invalid, regenerating invoke code", invokedContext->m_queryString); + attemptRegen = true; + } + + if (attemptRegen) { + JitCodegenState result = RegenerateQuery(jitSource); + if (result != JitCodegenState::JIT_CODEGEN_READY) { + MOT_LOG_TRACE("Code regeneration resulted in state %s for query: %s", + JitCodegenStateToString(result), + jitSource->m_queryString); + return result; + } + // pointers need to be fetched again, since they changed after full regeneration + LockJitSource(jitSource); + queryContext = (JitQueryContext*)jitSource->m_sourceJitContext; + invokedContext = queryContext->m_invokeContext; + UnlockJitSource(jitSource); + } + + MOT_LOG_TRACE("Re-validated invoked context: %s", invokedContext->m_queryString); + MOT_LOG_TRACE("Re-validated JIT invoke query source %p of query: %s", jitSource, jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_READY; +} + +static JitCodegenState RevalidateJitQuerySource(JitSource* jitSource, JitCodegenState prevState) +{ + MOT_LOG_TRACE("Re-validating JIT query source %p with query: %s", jitSource, jitSource->m_queryString); + + JitCodegenState regenResult = JitCodegenState::JIT_CODEGEN_NONE; + int regenError = MOT_ERROR_RESOURCE_UNAVAILABLE; + if ((prevState == JitCodegenState::JIT_CODEGEN_EXPIRED) || (jitSource->m_sourceJitContext == nullptr)) { + // case 1: full code regeneration is required (includes invalid invoke context) + regenResult = RegenerateQuery(jitSource); + } else if ((prevState == JitCodegenState::JIT_CODEGEN_READY) || + (prevState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) || + (prevState == JitCodegenState::JIT_CODEGEN_PENDING)) { + // case 2: query is invalid, this is possible only with INVOKE query or when table pointers are invalid + if (jitSource->m_commandType == JIT_COMMAND_INVOKE) { + regenResult = RevalidateJitInvokeQuery(jitSource); + } else { + // No concurrent revalidation possible for normal queries. + MOT_ASSERT(MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState) == JIT_CONTEXT_RELATION_INVALID); + if (!RefetchTablesAndIndices(jitSource->m_sourceJitContext)) { + MOT_LOG_TRACE("Failed to re-fetch tables and index objects for source %p", jitSource); + MOT_ATOMIC_STORE(jitSource->m_sourceJitContext->m_validState, JIT_CONTEXT_INVALID); + regenResult = JitCodegenState::JIT_CODEGEN_ERROR; + regenError = MOT_ERROR_INTERNAL; + } else { + MOT_ATOMIC_STORE(jitSource->m_sourceJitContext->m_validState, JIT_CONTEXT_VALID); + regenResult = JitCodegenState::JIT_CODEGEN_READY; + } + } + } else { + MOT_LOG_TRACE("RevalidateJitQuerySource(): Unexpected codegen state %s", JitCodegenStateToString(prevState)); + regenResult = JitCodegenState::JIT_CODEGEN_ERROR; + regenError = MOT_ERROR_INVALID_STATE; + } + + if (regenResult == JitCodegenState::JIT_CODEGEN_READY) { + // It is possible that the source is deprecated concurrently, so we check that within JIT source lock inside + // ReinstateReadyState and then set source context as valid. + regenResult = ReinstateReadyState(jitSource); + } else if (regenResult == JitCodegenState::JIT_CODEGEN_ERROR) { + MOT_LOG_TRACE("Failed to regenerate code for query text: %s", jitSource->m_queryString); + SetJitSourceError(jitSource, regenError, ®enResult); + } else if (regenResult == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + // we can get here a "pending compilation" result if a different invoking query regenerates code for the same + // SP as this query. In this case we mark the invoking query as "pending compilation". When the SP finishes + // compilation, this invoke query will be marked as "done-compiling" to re-attempt revalidation on next + // invocation (see JitSourceInformCompileDone() for remark on this special use-case) + LockJitSource(jitSource); + MOT_ASSERT(jitSource->m_commandType == JIT_COMMAND_INVOKE); + MOT_LOG_TRACE("RevalidateJitQuerySource(): Pending compilation of invoked query source %p (state %s)", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState)); + regenResult = jitSource->m_codegenState; + if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_DEPRECATE) { + // a race condition is possible with the session that compiles the common SP, so be careful + if (jitSource->m_pendingChildCompile == 0) { + jitSource->m_pendingChildCompile = JIT_CONTEXT_PENDING_COMPILE; + } + // even if race condition occurred, we still allow caller to collect the result on next invocation + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_PENDING; + regenResult = JitCodegenState::JIT_CODEGEN_PENDING; + } + UnlockJitSource(jitSource); + } else if (regenResult == JitCodegenState::JIT_CODEGEN_PENDING) { + MOT_LOG_TRACE("RevalidateJitQuerySource(): Pending compilation of invoked query"); + } else { + MOT_LOG_TRACE( + "RevalidateJitQuerySource(): Unexpected resulting codegen state %s", JitCodegenStateToString(regenResult)); + SetJitSourceError(jitSource, MOT_ERROR_INVALID_STATE, ®enResult); + } + + return regenResult; +} + +static bool RevalidateJitFunction(JitSource* jitSource) +{ + MOT_LOG_TRACE("Re-validating JIT function %p (%p) with function: %s", + jitSource, + jitSource->m_sourceJitContext, + jitSource->m_queryString); + + JitFunctionContext* functionContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + uint8_t validState = MOT_ATOMIC_LOAD(functionContext->m_validState); + if (validState & JIT_CONTEXT_INVALID) { + // case 1: function context itself is invalid and needs to be fully regenerated, we must wait for user to issue + // "CREATE OR REPLACE FUNCTION" + MOT_LOG_TRACE("Cannot revalidate dropped JIT function: %s, validState: %s", + jitSource->m_queryString, + JitContextValidStateToString(validState)); + return false; + } + + // case 2: the underlying function context itself is still valid (some sub-query/SP needs to be re-validated) + MOT_LOG_TRACE("Attempting to revalidate any invalid query in SP: %s", jitSource->m_queryString); + if (!RevalidateJitContext(functionContext)) { + MOT_LOG_TRACE("Failed to revalidate JIT function: %s", jitSource->m_queryString); + return false; + } + + bool result = true; + // It is possible that the source is deprecated concurrently, so we check that within JIT source lock inside + // ReinstateReadyState and then set source context as valid. + if (ReinstateReadyState(jitSource) != JitCodegenState::JIT_CODEGEN_READY) { + result = false; + } + + MOT_LOG_TRACE("Re-validated JIT function source %p with function: %s", jitSource, jitSource->m_queryString); + return result; +} + +static JitCodegenState ProcessRegeneratedJitFunctionSource(JitSource* jitSource, MotJitContext* sourceJitContext, + JitCodegenStats* codegenStats, const char* qualifiedFunctionName) +{ + JitCodegenState newState = JitCodegenState::JIT_CODEGEN_NONE; + if (sourceJitContext != nullptr) { + (void)SetJitSourceReady(jitSource, (MotJitContext*)sourceJitContext, codegenStats, &newState); + } else { + SetJitSourceError(jitSource, MOT_ERROR_RESOURCE_UNAVAILABLE, &newState); + } + + if (newState == JitCodegenState::JIT_CODEGEN_DEPRECATE) { + // ATTENTION: Do not use the jitSource after this point, as it might be removed from the deprecated list + // and freed by other sessions. + CleanupConcurrentlyDroppedSPSource((const char*)qualifiedFunctionName); + } else if (newState == JitCodegenState::JIT_CODEGEN_READY) { + bool sourceReady = false; + if (jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + LockJitSourceMap(); + + LockJitSource(jitSource); + newState = jitSource->m_codegenState; + if (jitSource->m_sourceJitContext == sourceJitContext) { + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + sourceReady = true; + } else { + MOT_LOG_TRACE("JIT function source %p has unexpected state %s, after re-generation: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + } + } else { + MOT_LOG_TRACE("JIT function source %p context %p changed concurrently to %p after re-generation", + jitSource, + sourceJitContext, + jitSource->m_sourceJitContext); + } + UnlockJitSource(jitSource); + + if (sourceReady) { + MOT_LOG_TRACE("Pruning global ready function source %p after re-generation: %s", + jitSource, + jitSource->m_queryString); + PruneNamespace(jitSource); + } + + UnlockJitSourceMap(); + } + } + + return newState; +} + +static JitCodegenState RegenerateJitFunctionSource(JitSource* jitSource) +{ + MOT_LOG_TRACE("Regenerating JIT function source %p: %s", jitSource, jitSource->m_queryString); + // we fetch again function name by id so we can validate function still exists + const char* functionName = get_func_name(jitSource->m_functionOid); + if (functionName == nullptr) { + // function is not found, so we set state to dropped + MOT_LOG_TRACE("RegenerateJitFunctionSource(): cannot find function %u", jitSource->m_functionOid); + LockJitSource(jitSource); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_DROPPED; + UnlockJitSource(jitSource); + return JitCodegenState::JIT_CODEGEN_DROPPED; + } + + if (u_sess->mot_cxt.jit_compile_depth > MOT_JIT_MAX_COMPILE_DEPTH) { + MOT_LOG_TRACE("RegenerateJitFunctionSource(): Reached maximum compile depth"); + SetJitSourceError(jitSource, MOT_ERROR_RESOURCE_LIMIT); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + MOT::mot_string qualifiedFunctionName; + if (!qualifiedFunctionName.format("%s.%u", functionName, jitSource->m_functionOid)) { + MOT_LOG_TRACE("RegenerateJitFunctionSource(): Failed to format qualified function name"); + SetJitSourceError(jitSource, MOT_ERROR_RESOURCE_LIMIT); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + // every variable used after catch needs to be volatile (see longjmp() man page) + volatile HeapTuple procTuple = nullptr; + volatile PLpgSQL_function* func = nullptr; + volatile JitPlan* plan = nullptr; + volatile MotJitContext* sourceJitContext = nullptr; + volatile bool nsPushed = false; + JitCodegenStats codegenStats = {}; // accessed only when ereport was not thrown + volatile MemoryContext origCxt = CurrentMemoryContext; + volatile const char* qualifiedFunctionNameStr = qualifiedFunctionName.c_str(); + PG_TRY(); + { + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(jitSource->m_functionOid)); + if (!HeapTupleIsValid(procTuple)) { + MOT_LOG_TRACE("RegenerateJitFunctionSource(): Oid %u not found in pg_proc", jitSource->m_functionOid); + } else { + // now we trigger compilation of the function at this point unconditionally + // attention: if compilation failed then ereport is thrown. + func = GetPGCompiledFunction(jitSource->m_functionOid, functionName); + if (func == nullptr) { + MOT_LOG_TRACE("RegenerateJitFunctionSource(): No compilation result for function %s", functionName); + SetJitSourceError(jitSource, MOT_ERROR_RESOURCE_LIMIT); + } else { + ++func->use_count; + MOT_LOG_TRACE("RegenerateJitFunctionSource(): Increased use count of function %p to %lu: %s", + func, + func->use_count, + jitSource->m_queryString); + + MOT_ASSERT(func->fn_xmin > jitSource->m_functionTxnId && func->fn_xmin >= jitSource->m_expireTxnId); + + // now we try to generate JIT code for the function + if (PushJitSourceNamespace(jitSource->m_functionOid, jitSource->m_queryString)) { + nsPushed = true; + plan = IsJittableFunction((PLpgSQL_function*)func, procTuple, jitSource->m_functionOid, true); + if (plan != nullptr) { + uint64_t startTime = GetSysClock(); + MOT_LOG_TRACE("Generating LLVM JIT context for function: %s", functionName); + sourceJitContext = JitCodegenLlvmFunction((PLpgSQL_function*)func, + procTuple, + jitSource->m_functionOid, + nullptr, + (JitPlan*)plan, + codegenStats); + uint64_t endTime = GetSysClock(); + uint64_t timeMicros = MOT::CpuCyclesLevelTime::CyclesToMicroseconds(endTime - startTime); + codegenStats.m_codegenTime = timeMicros; + ((JitFunctionPlan*)plan)->m_function = nullptr; // prevent decrease use count + JitDestroyPlan((JitPlan*)plan); + plan = nullptr; + } + } + } + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while regenerating function %s: %s", functionName, edata->message); + ereport(WARNING, + (errmodule(MOD_MOT), + errmsg("Caught exception while regenerating function %s: %s", functionName, edata->message), + errdetail("%s", edata->detail))); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + // reset compile state for robustness + JitResetCompileState(); + + if (plan != nullptr) { + JitDestroyPlan((JitPlan*)plan); + } + + if (nsPushed) { + PopJitSourceNamespace(); + } + + if (func != nullptr) { + --func->use_count; + MOT_LOG_TRACE("RegenerateJitFunctionSource(): Decreased use count of function %p to %lu: %s", + func, + func->use_count, + jitSource->m_queryString); + } + + if (procTuple != nullptr) { + ReleaseSysCache(procTuple); + } + + return ProcessRegeneratedJitFunctionSource( + jitSource, (MotJitContext*)sourceJitContext, &codegenStats, (const char*)qualifiedFunctionNameStr); +} + +static JitCodegenState RevalidateJitFunctionSource(JitSource* jitSource, JitCodegenState prevState) +{ + MOT_LOG_TRACE("Re-validating JIT function source %p with function: %s", jitSource, jitSource->m_queryString); + + // case 1: function was dropped or being replaced. If function was not created yet we will proceed to full + // regeneration. otherwise we have nothing to do about it. + if ((prevState == JitCodegenState::JIT_CODEGEN_DROPPED) || (prevState == JitCodegenState::JIT_CODEGEN_REPLACED)) { + MOT_LOG_TRACE("RevalidateJitFunctionSource(): Function was dropped or replaced, attempting full code " + "regeneration for SP: %s", + jitSource->m_queryString); + JitCodegenState result = RegenerateJitFunctionSource(jitSource); + if (result == JitCodegenState::JIT_CODEGEN_DROPPED) { + // Function was dropped. We return JitCodegenState::JIT_CODEGEN_ERROR to indicate the re-validation failure. + MOT_LOG_TRACE("RevalidateJitFunctionSource(): Function was dropped, signaling codegen error for SP: %s", + jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + return result; + } + + // lock before accessing source JIT context + (void)pthread_mutex_lock(&jitSource->m_lock); + + // any other state but ready is unexpected and leads to error state + if ((prevState != JitCodegenState::JIT_CODEGEN_READY) || (jitSource->m_sourceJitContext == nullptr)) { + MOT_LOG_TRACE( + "RevalidateJitFunctionSource(): Unexpected codegen state %s (source context %p, current state %s)", + JitCodegenStateToString(prevState), + jitSource->m_sourceJitContext, + JitCodegenStateToString(jitSource->m_codegenState)); + (void)pthread_mutex_unlock(&jitSource->m_lock); + SetJitSourceError(jitSource, MOT_ERROR_INVALID_STATE); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + // case 3: function is ready but invalid, so we re-validate it outside lock scope + MOT_ASSERT(MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState) != JIT_CONTEXT_VALID); + + (void)pthread_mutex_unlock(&jitSource->m_lock); + + MOT_LOG_TRACE("RevalidateJitFunctionSource(): Function is ready but invalid, attempting revalidation for SP: %s ", + jitSource->m_queryString); + if (!RevalidateJitFunction(jitSource)) { + MOT_LOG_TRACE( + "RevalidateJitFunctionSource(): Failed to revalidate code for function: %s", jitSource->m_queryString); + SetJitSourceError(jitSource, MOT_ERROR_RESOURCE_UNAVAILABLE); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + return JitCodegenState::JIT_CODEGEN_READY; +} + +extern JitCodegenState RevalidateJitSource( + JitSource* jitSource, TransactionId functionTxnId /* = InvalidTransactionId */) +{ + MOT_LOG_TRACE("Re-validating JIT source %p with query: %s", jitSource, jitSource->m_queryString); + + (void)pthread_mutex_lock(&jitSource->m_lock); + volatile bool needUnlock = true; + + // we cannot revalidate deprecate JIT source + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DEPRECATE) { + MOT_LOG_TRACE("Cannot revalidate deprecate JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_DEPRECATE; + } + + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) { + MOT_LOG_TRACE("Cannot revalidate dropped JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_DROPPED; + } + + // If someone else is operating on the source then we just inform the caller + // that the code is being generated by returning JitCodegenState::JIT_CODEGEN_UNAVAILABLE. + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + MOT_LOG_TRACE( + "Backing-off from revalidation in unavailable JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + } + + // if the source was previously marked as pending for child compilation to finish, then check again + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_PENDING) { + if (jitSource->m_pendingChildCompile == JIT_CONTEXT_PENDING_COMPILE) { + MOT_LOG_TRACE( + "Backing-off from revalidation in pending JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + return JitCodegenState::JIT_CODEGEN_PENDING; + } else { + // whether done or error, just reset and retry + MOT_LOG_TRACE( + "Child compile done/error/none in pending JIT source %p: %s", jitSource, jitSource->m_queryString); + jitSource->m_pendingChildCompile = 0; + } + } + + // if everything is fine then just return + volatile JitCodegenState result = jitSource->m_codegenState; + uint8_t validState = JIT_CONTEXT_INVALID; + if (jitSource->m_sourceJitContext != nullptr) { + validState = MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState); + } + MOT_LOG_TRACE("JIT source %p with source context %p is in state %s, Valid-state is: %s", + jitSource, + jitSource->m_sourceJitContext, + JitCodegenStateToString(jitSource->m_codegenState), + JitContextValidStateToString(validState)); + if ((result == JitCodegenState::JIT_CODEGEN_READY) && (validState == JIT_CONTEXT_VALID)) { + MOT_LOG_TRACE("JIT source %p is ready and valid", jitSource); + } else { + if (!IsSimpleQuerySource(jitSource) && + ((result == JitCodegenState::JIT_CODEGEN_EXPIRED) || (result == JitCodegenState::JIT_CODEGEN_ERROR)) && + IsPrematureRevalidation(jitSource, functionTxnId)) { + MOT_LOG_TRACE("Skipping premature re-validation of JIT source %p: %s", jitSource, jitSource->m_queryString); + (void)pthread_mutex_unlock(&jitSource->m_lock); + // Premature re-validation attempt: We don't change the codegen state. + // We return to caller status-deprecate, this way the caller will abort this codegen attempt. + return JitCodegenState::JIT_CODEGEN_DEPRECATE; + } + + MOT_LOG_TRACE("JIT source %p moving from state %s to UNAVAILABLE before re-validation: %s", + jitSource, + JitCodegenStateToString(result), + jitSource->m_queryString); + + // we set to unavailable state regardless of current state (whether expired due to DDL, + // or child query/sp invalid, or dropped/replaced function, or even error state) + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_UNAVAILABLE; + + // revalidate/regenerate out of lock scope, so mot_jit_details does not get stuck + (void)pthread_mutex_unlock(&jitSource->m_lock); + needUnlock = false; + + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = RevalidateJitQuerySource(jitSource, result); + } else { + result = RevalidateJitFunctionSource(jitSource, result); + } + if (result == JitCodegenState::JIT_CODEGEN_READY) { + JitSourceList parentSourceList; + (void)pthread_mutex_lock(&jitSource->m_lock); + // attention: concurrent deprecate can happen, so we check again after re-locking + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + JitSourceInformCompileDone(jitSource, &parentSourceList); + } + result = jitSource->m_codegenState; // report deprecate if race happened + (void)pthread_mutex_unlock(&jitSource->m_lock); + // notify outside lock-scope to avoid deadlock + JitSourceListInformCompileDone(&parentSourceList, jitSource->m_queryString); + } + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while re-validating JIT source for query '%s': %s", + jitSource->m_queryString, + edata->message); + FlushErrorState(); + FreeErrorData(edata); + SetJitSourceError(jitSource, MOT_ERROR_SYSTEM_FAILURE); + result = JitCodegenState::JIT_CODEGEN_ERROR; + } + PG_END_TRY(); + } + + if (needUnlock) { + (void)pthread_mutex_unlock(&jitSource->m_lock); + } + + MOT_LOG_TRACE("Re-validate result %s for JIT source %p with query: %s", + JitCodegenStateToString(result), + jitSource, + jitSource->m_queryString); + return result; +} + +extern void SetJitSourceError(JitSource* jitSource, int errorCode, JitCodegenState* newState /* = nullptr */) +{ + JitCodegenStats codegenStats = {}; // Reset the code generation stats on error. + SetJitSourceStatus( + jitSource, nullptr, JitCodegenState::JIT_CODEGEN_ERROR, &codegenStats, errorCode, 0, nullptr, false, newState); +} + +extern void SetJitSourceExpired(JitSource* jitSource, uint64_t relationId, bool functionReplace /* = false */) +{ + // We don't reset the code generation stats when setting the source as expired. + SetJitSourceStatus( + jitSource, nullptr, JitCodegenState::JIT_CODEGEN_EXPIRED, nullptr, 0, relationId, nullptr, functionReplace); JitStatisticsProvider::GetInstance().AddCodeExpiredQuery(); } -extern bool SetJitSourceReady(JitSource* jitSource, JitContext* readySourceJitContext) +extern bool SetJitSourceReady(JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenStats* codegenStats, + JitCodegenState* newState /* = nullptr */) { bool result = true; bool invalidState = false; - SetJitSourceStatus(jitSource, readySourceJitContext, JIT_CONTEXT_READY, true, 0, 0, &invalidState); + MOT_ASSERT(codegenStats != nullptr); + SetJitSourceStatus(jitSource, + readySourceJitContext, + JitCodegenState::JIT_CODEGEN_READY, + codegenStats, + 0, + 0, + &invalidState, + false, + newState); if (invalidState) { result = false; } return result; } -static char* CloneQueryString(const char* queryString) +static char* CloneQueryString(const char* queryString, JitContextUsage usage) { - char* newQueryString = NULL; + char* newQueryString = nullptr; if (queryString != nullptr) { size_t len = strlen(queryString); if (len > 0) { - newQueryString = (char*)MOT::MemGlobalAlloc(len + 1); + newQueryString = (char*)JitMemAlloc(len + 1, usage); if (newQueryString != nullptr) { errno_t erc = strcpy_s(newQueryString, len + 1, queryString); securec_check(erc, "\0", "\0"); @@ -211,187 +1340,881 @@ static char* CloneQueryString(const char* queryString) return newQueryString; } -static void FreeQueryString(char* queryString) +static void FreeQueryString(char* queryString, JitContextUsage usage) { if (queryString != nullptr) { - MOT::MemGlobalFree(queryString); + JitMemFree(queryString, usage); } } -static char* ReallocQueryString(char* oldQueryString, const char* newQueryString) +static bool CheckInvalidStateTransition( + JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenState state, bool* invalidState) { - if (oldQueryString == nullptr) { - oldQueryString = CloneQueryString(newQueryString); - } else if (newQueryString == nullptr) { - FreeQueryString(oldQueryString); - oldQueryString = NULL; - } else { - size_t oldLength = strlen(oldQueryString); - size_t newLength = strlen(newQueryString); - if (newLength < oldLength) { - errno_t erc = - strncpy_s(oldQueryString, oldLength, newQueryString, newLength + 1); // copy terminating null too - securec_check(erc, "\0", "\0"); - } else { - newQueryString = (char*)MOT::MemGlobalRealloc(oldQueryString, newLength, MOT::MEM_REALLOC_COPY_ZERO); - } - } - return oldQueryString; -} - -static void SetJitSourceStatus(JitSource* jitSource, JitContext* readySourceJitContext, JitContextStatus status, - bool notifyAll /* = true */, int errorCode /* = 0 */, uint64_t relationId /* = 0 */, - bool* invalidState /* = nullptr */) -{ - MOT::Table* table = readySourceJitContext ? readySourceJitContext->m_table : NULL; - MOT_LOG_TRACE("Jit source %p: Installing ready JIT context %p with status %s, table %p and query: %s", - jitSource, - readySourceJitContext, - JitContextStatusToString(status), - table, - jitSource->_query_string); - - pthread_mutex_lock(&jitSource->_lock); - - if ((jitSource->_source_jit_context != NULL) && (status == JIT_CONTEXT_READY)) { + // ready --> ready + if ((jitSource->m_sourceJitContext != nullptr) && + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) && + (state == JitCodegenState::JIT_CODEGEN_READY)) { MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, "JIT Compile", "Cannot set context as ready: already set"); if (invalidState) { *invalidState = true; } - } else { - MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, status); - if (jitSource->_status == JIT_CONTEXT_EXPIRED) { - if (status != JIT_CONTEXT_EXPIRED) { - MOT_LOG_TRACE("Fixed expired JIT source %p with context %p (query string: %s)", - jitSource, - readySourceJitContext, - jitSource->_query_string); - } else { - MOT_LOG_TRACE("Duplicate attempt to set JIT source %p as expired (query string: %s)", - jitSource, - jitSource->_query_string); - } - } else if (status == JIT_CONTEXT_EXPIRED) { - MOT_LOG_TRACE("Setting JIT source %p as expired (query string: %s)", jitSource, jitSource->_query_string); - } else { - MOT_LOG_TRACE("Installing JIT source %p with context %p (query string: %s)", + return false; + } + + // deprecate --> not deprecate + if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DEPRECATE) && + (state != JitCodegenState::JIT_CODEGEN_DEPRECATE)) { + MOT_LOG_ERROR("Attempt to set deprecate JIT source %p as %s denied: %s", + jitSource, + JitCodegenStateToString(state), + jitSource->m_queryString); + if (invalidState) { + *invalidState = true; + } + return false; + } + + MOT_JIT_SOURCE_VERIFY_STATE(jitSource, readySourceJitContext, state); + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) { + if (state != JitCodegenState::JIT_CODEGEN_EXPIRED) { + MOT_LOG_TRACE("Updated expired JIT source %p with context %p to state %s (query string: %s)", jitSource, readySourceJitContext, - jitSource->_query_string); + JitCodegenStateToString(state), + jitSource->m_queryString); + } else { + MOT_LOG_TRACE("Duplicate attempt to set JIT source %p as expired (query string: %s)", + jitSource, + jitSource->m_queryString); } - // cleanup old context (avoid resource leak) - if (jitSource->_source_jit_context != nullptr) { - DestroyJitContext(jitSource->_source_jit_context); - } - jitSource->_source_jit_context = readySourceJitContext; - jitSource->_status = status; - if (jitSource->_source_jit_context != nullptr) { - // copy query string from source to context - jitSource->_source_jit_context->m_queryString = jitSource->_query_string; - jitSource->m_contextList = nullptr; - } else { // expired context: cleanup all related JIT context objects - JitSourcePurgeContextList(jitSource, relationId); + } else if (state == JitCodegenState::JIT_CODEGEN_EXPIRED) { + MOT_LOG_TRACE("Setting JIT source %p as expired (query string: %s)", jitSource, jitSource->m_queryString); + } else { + MOT_LOG_TRACE("Installing JIT source %p with context %p (query string: %s)", + jitSource, + readySourceJitContext, + jitSource->m_queryString); + } + return true; +} + +static void SetJitSourceJittable(JitSource* jitSource, bool& compileDone, JitSourceList& parentSourceList) +{ + // Valid source context and state are just installed in the caller (in SetJitSourceStatus()). + MOT_ASSERT(jitSource->m_sourceJitContext != nullptr); + + MotJitContext* readySourceJitContext = jitSource->m_sourceJitContext; + + // copy query string from source to context + jitSource->m_sourceJitContext->m_queryString = jitSource->m_queryString; + jitSource->m_sourceJitContext->m_jitSource = jitSource; + jitSource->m_sourceJitContext->m_isSourceContext = 1; + MOT_ASSERT(jitSource->m_contextType == readySourceJitContext->m_contextType); + jitSource->m_commandType = readySourceJitContext->m_commandType; + jitSource->m_status = JitSourceStatus::JIT_SOURCE_JITTABLE; + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_INVALID) { + jitSource->m_contextType = readySourceJitContext->m_contextType; + } + MOT_ASSERT(jitSource->m_contextType == readySourceJitContext->m_contextType); + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + jitSource->m_functionOid = ((JitFunctionContext*)readySourceJitContext)->m_functionOid; + jitSource->m_functionTxnId = ((JitFunctionContext*)readySourceJitContext)->m_functionTxnId; + jitSource->m_expireTxnId = 0; + } else { + if (jitSource->m_commandType == JIT_COMMAND_INVOKE) { + if (((JitQueryContext*)readySourceJitContext)->m_invokeContext != nullptr) { + jitSource->m_functionOid = + ((JitQueryContext*)readySourceJitContext)->m_invokeContext->m_functionOid; + jitSource->m_functionTxnId = + ((JitQueryContext*)readySourceJitContext)->m_invokeContext->m_functionTxnId; + } else { + jitSource->m_functionOid = ((JitQueryContext*)readySourceJitContext)->m_invokedFunctionOid; + jitSource->m_functionTxnId = + ((JitQueryContext*)readySourceJitContext)->m_invokedFunctionTxnId; + } + jitSource->m_expireTxnId = 0; } } - pthread_mutex_unlock(&jitSource->_lock); - - // in any case we notify change (even error or expired status) - MOT_LOG_TRACE("%p: Notifying JIT context %p is ready (query: %s)", - jitSource, - jitSource->_source_jit_context, - jitSource->_query_string); - - if (notifyAll) { - pthread_cond_broadcast(&jitSource->_cond); + if ((jitSource->m_commandType == JIT_COMMAND_INVOKE) && + (((JitQueryContext*)readySourceJitContext)->m_invokeContext != nullptr) && + IsJitContextPendingCompile(((JitQueryContext*)readySourceJitContext)->m_invokeContext)) { + MOT_LOG_TRACE("Installed pending source context: %s", jitSource->m_queryString); + jitSource->m_pendingChildCompile = JIT_CONTEXT_PENDING_COMPILE; + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_PENDING; } else { - pthread_cond_signal(&jitSource->_cond); + JitSourceInformCompileDone(jitSource, &parentSourceList); + compileDone = true; + MOT_LOG_TRACE("Compilation done: %s", jitSource->m_queryString); } } -static const char* JitContextStatusToString(JitContextStatus status) +static void SetJitSourceUnjittable( + JitSource* jitSource, uint64_t relationId, bool functionReplace, JitSourceList& parentSourceList) { - switch (status) { - case JIT_CONTEXT_READY: - return "Ready"; - case JIT_CONTEXT_UNAVAILABLE: - return "Unavailable"; - case JIT_CONTEXT_ERROR: - return "Error"; - case JIT_CONTEXT_EXPIRED: - return "Expired"; + // Null source context and state are just installed in the caller (in SetJitSourceStatus()). + MOT_ASSERT(jitSource->m_sourceJitContext == nullptr); + + JitCodegenState state = jitSource->m_codegenState; + + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType != JIT_COMMAND_INVOKE)) { + JitSourcePurgeContextList(jitSource, relationId); + } + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) { + JitSourceInvalidateContextList(jitSource, relationId); + } else if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_ERROR) { + JitSourceInformCompileError(jitSource, &parentSourceList); + } else { + MOT_LOG_TRACE("Invalid codegen state: %s", JitCodegenStateToString(jitSource->m_codegenState)); + MOT_ASSERT(false); + } + jitSource->m_status = JitSourceStatus::JIT_SOURCE_UNJITTABLE; + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + if (state != JitCodegenState::JIT_CODEGEN_ERROR) { + jitSource->m_status = JitSourceStatus::JIT_SOURCE_INVALID; // move to transient state + } + } else { + // unless error state, we fix state to replaced or dropped + if (state != JitCodegenState::JIT_CODEGEN_ERROR) { + if (functionReplace) { + // this is a short lived transient state, which is used to force code regeneration + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_REPLACED; + jitSource->m_status = JitSourceStatus::JIT_SOURCE_INVALID; // move to transient state + } else { + // function is dropped + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_DROPPED; + } + } + } +} + +static void SetJitSourceStatus(JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenState state, + JitCodegenStats* codegenStats /* = nullptr */, int errorCode /* = 0 */, uint64_t relationId /* = 0 */, + bool* invalidState /* = nullptr */, bool functionReplace /* = false */, JitCodegenState* newState /* = nullptr */) +{ + MOT::Table* table = nullptr; + if (readySourceJitContext && readySourceJitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + table = ((JitQueryContext*)readySourceJitContext)->m_table; + } + + JitSourceList parentSourceList; + bool compileDone = false; + MotJitContext* recyclableContext = nullptr; + (void)pthread_mutex_lock(&jitSource->m_lock); + + MOT_LOG_TRACE("JIT source %p (codegen state %s, deprecatedPendingCompile %u): Installing ready JIT source context " + "%p with status %s, table %p and query: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_deprecatedPendingCompile, + readySourceJitContext, + JitCodegenStateToString(state), + table, + jitSource->m_queryString); + MOT_LOG_TRACE("JIT source %p: relation-id = %" PRIu64 ", function-replace = %s", + jitSource, + relationId, + functionReplace ? "true" : "false"); + + if (jitSource->m_deprecatedPendingCompile) { + // JIT source was deprecated by another session when we were compiling. + // Allow the JIT source to be cleaned from deprecated list now. + DeprecatePendingCompileSource(jitSource, &recyclableContext); + JitSourceInformCompileError(jitSource, &parentSourceList); + if (invalidState) { + *invalidState = true; + } + } else { + if (CheckInvalidStateTransition(jitSource, readySourceJitContext, state, invalidState)) { + // cleanup old context (avoid resource leak) + if (jitSource->m_sourceJitContext != nullptr) { + // delay the destruction of the source context until any session is done executing + // the old source context + jitSource->m_sourceJitContext->m_jitSource = nullptr; // avoid warning + DeprecateSourceJitContext(jitSource, &recyclableContext); + } + + // install new context + jitSource->m_sourceJitContext = readySourceJitContext; + jitSource->m_codegenState = state; + if (jitSource->m_sourceJitContext != nullptr) { + SetJitSourceJittable(jitSource, compileDone, parentSourceList); + } else { + // expired/error/dropped/replaced context: cleanup all related JIT context objects + SetJitSourceUnjittable(jitSource, relationId, functionReplace, parentSourceList); + } + jitSource->m_timestamp = GetCurrentTimestamp(); + if (codegenStats != nullptr) { + jitSource->m_codegenStats = *codegenStats; + } + } + } + + JitCodegenState finalState = jitSource->m_codegenState; + if (newState != nullptr) { + *newState = jitSource->m_codegenState; + } + + (void)pthread_mutex_unlock(&jitSource->m_lock); + + if (recyclableContext != nullptr) { + DestroyJitContext(recyclableContext); + recyclableContext = nullptr; + } + + // notify outside lock-scope to avoid deadlock + if (!parentSourceList.Empty()) { + if (compileDone) { + JitSourceListInformCompileDone(&parentSourceList, jitSource->m_queryString); + } else { + JitSourceListInformCompileError(&parentSourceList, jitSource->m_queryString); + } + } + + // in any case we notify change (even error or expired status) + MOT_LOG_TRACE("JIT source %p: Notifying JIT context %p is %s (query: %s)", + jitSource, + jitSource->m_sourceJitContext, + JitCodegenStateToString(finalState), + jitSource->m_queryString); +} + +extern const char* JitCodegenStateToString(JitCodegenState state) +{ + switch (state) { + case JitCodegenState::JIT_CODEGEN_READY: + return "ready"; + case JitCodegenState::JIT_CODEGEN_UNAVAILABLE: + return "unavailable"; + case JitCodegenState::JIT_CODEGEN_ERROR: + return "error"; + case JitCodegenState::JIT_CODEGEN_EXPIRED: + return "expired"; + case JitCodegenState::JIT_CODEGEN_DROPPED: + return "dropped"; + case JitCodegenState::JIT_CODEGEN_REPLACED: + return "replaced"; + case JitCodegenState::JIT_CODEGEN_DEPRECATE: + return "deprecate"; + case JitCodegenState::JIT_CODEGEN_PENDING: + return "pending"; + case JitCodegenState::JIT_CODEGEN_NONE: + return "none"; default: return "N/A"; } } -extern void AddJitSourceContext(JitSource* jitSource, JitContext* cleanupContext) +extern void AddJitSourceContext(JitSource* jitSource, MotJitContext* jitContext) { - pthread_mutex_lock(&jitSource->_lock); - cleanupContext->m_nextInSource = jitSource->m_contextList; - jitSource->m_contextList = cleanupContext; - pthread_mutex_unlock(&jitSource->_lock); + (void)pthread_mutex_lock(&jitSource->m_lock); + MOT_ASSERT(!ContextListContainsJitContext(jitSource, jitContext)); + jitContext->m_nextInSource = jitSource->m_contextList; + jitSource->m_contextList = jitContext; + jitContext->m_jitSource = jitSource; + jitContext->m_sourceJitContext = jitSource->m_sourceJitContext; + if (jitSource->m_sourceJitContext != nullptr) { + MOT_ATOMIC_INC(jitSource->m_sourceJitContext->m_useCount); + MOT_LOG_TRACE("AddJitSourceContext(): Incremented use count of source context %p to: %u", + jitSource->m_sourceJitContext, + (uint32_t)MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_useCount)); + } + MOT_LOG_TRACE("JIT source %p: Added JIT context %p", jitSource, jitContext); + (void)pthread_mutex_unlock(&jitSource->m_lock); } -extern void RemoveJitSourceContext(JitSource* jitSource, JitContext* cleanupContext) +extern void RemoveJitSourceContext(JitSource* jitSource, MotJitContext* cleanupContext) { - pthread_mutex_lock(&jitSource->_lock); + bool canRecycle = false; + MotJitContext* deprecatedContext = nullptr; + (void)pthread_mutex_lock(&jitSource->m_lock); + // by the time we reach here, many things could have happened, so we first need to understand whether this context + // originated from the current source context or from a deprecate one + // even if source context is deprecate, the context is still found in the context list RemoveJitSourceContextImpl(jitSource, cleanupContext); - pthread_mutex_unlock(&jitSource->_lock); -} - -extern bool JitSourceRefersRelation(JitSource* jitSource, uint64_t relationId) -{ - bool result = false; - JitContext* srcContext = jitSource->_source_jit_context; - if (srcContext != nullptr) { // context not expired - if ((srcContext->m_table != nullptr) && (srcContext->m_table->GetTableExId() == relationId)) { - result = true; - } else if ((srcContext->m_innerTable != nullptr) && (srcContext->m_innerTable->GetTableExId() == relationId)) { - result = true; - } else if (srcContext->m_subQueryCount > 0) { - for (uint32_t i = 0; i < srcContext->m_subQueryCount; ++i) { - if ((srcContext->m_subQueryData[i].m_table != nullptr) && - (srcContext->m_subQueryData[i].m_table->GetTableExId() == relationId)) { - result = true; - break; - } + MotJitContext* sourceContext = cleanupContext->m_sourceJitContext; + if (sourceContext != nullptr) { + MOT_ATOMIC_DEC(sourceContext->m_useCount); + MOT_LOG_TRACE("RemoveJitSourceContext(): Decremented use count of source context %p to: %u", + sourceContext, + (uint32_t)MOT_ATOMIC_LOAD(sourceContext->m_useCount)); + if (sourceContext != jitSource->m_sourceJitContext) { + // we are dealing with a deprecate source context + MOT_ASSERT(MOT_ATOMIC_LOAD(sourceContext->m_validState) & JIT_CONTEXT_DEPRECATE); + MOT_ASSERT(DeprecateListContainsJitContext(jitSource, sourceContext)); + if (MOT_ATOMIC_LOAD(sourceContext->m_useCount) == 0) { + UnlinkDeprecateJitContext(jitSource, sourceContext); + deprecatedContext = sourceContext; } } + cleanupContext->m_sourceJitContext = nullptr; + } + // check if this is a deprecate JIT source that is ready for recycling + canRecycle = IsJitSourceRecyclable(jitSource); + (void)pthread_mutex_unlock(&jitSource->m_lock); + + MOT_LOG_TRACE("JIT source %p: Removed JIT context %p (%s)", jitSource, cleanupContext, jitSource->m_queryString); + + if (deprecatedContext != nullptr) { + MOT_LOG_TRACE("Destroying deprecate JIT source context %p", deprecatedContext); + DestroyJitContext(deprecatedContext); + deprecatedContext = nullptr; + } + + // schedule recycling out of lock scope to avoid deadlocks + if (canRecycle) { + MOT_LOG_TRACE("Deprecate JIT source %p is ready for recycling: %s", jitSource, jitSource->m_queryString); + ScheduleDeprecateJitSourceCleanUp(jitSource); + } +} + +extern bool JitSourceRefersRelation(JitSource* jitSource, uint64_t relationId, bool searchDeprecate) +{ + bool result = false; + (void)pthread_mutex_lock(&jitSource->m_lock); + if (searchDeprecate) { + MotJitContext* itr = jitSource->m_deprecateContextList; + while (itr != nullptr) { + if (JitContextRefersRelation(itr, relationId)) { + result = true; + break; + } + itr = itr->m_nextDeprecate; + } + } + if (!result) { + result = JitContextRefersRelation(jitSource->m_sourceJitContext, relationId); + } + (void)pthread_mutex_unlock(&jitSource->m_lock); + return result; +} + +extern bool JitSourceRefersSP(JitSource* jitSource, Oid functionId) +{ + bool result = false; + if (jitSource->m_sourceJitContext != nullptr) { + if (jitSource->m_sourceJitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + result = JitQueryContextRefersSP((JitQueryContext*)jitSource->m_sourceJitContext, functionId); + } else { + result = JitFunctionContextRefersSP((JitFunctionContext*)jitSource->m_sourceJitContext, functionId); + } + } + return result; +} + +static bool JitQueryContextRefersSP(JitQueryContext* jitContext, Oid functionId) +{ + bool result = false; + if (jitContext->m_commandType == JIT_COMMAND_INVOKE) { + result = JitFunctionContextRefersSP(jitContext->m_invokeContext, functionId); + } + return result; +} + +static bool JitFunctionContextRefersSP(JitFunctionContext* jitContext, Oid functionId) +{ + bool result = false; + if (jitContext->m_functionOid == functionId) { + result = true; + } else { + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (JitQueryContextRefersSP((JitQueryContext*)callSite->m_queryContext, functionId)) { + result = true; + break; + } + } + } + return result; +} + +extern uint32_t GetJitSourceNonJitQueryCount(JitSource* jitSource) +{ + if (jitSource->m_sourceJitContext == nullptr) { + return 0; + } + if (jitSource->m_sourceJitContext->m_contextType != JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + return 0; + } + uint32_t count = 0; + JitFunctionContext* jitContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (callSite->m_queryContext == nullptr) { + ++count; + } + } + return count; +} + +extern void PurgeJitSource(JitSource* jitSource, uint64_t relationId) +{ + (void)pthread_mutex_lock(&jitSource->m_lock); + + // purge source context + if (jitSource->m_sourceJitContext != nullptr) { + PurgeJitContext(jitSource->m_sourceJitContext, relationId); + } + + // purge registers sessions contexts + JitSourcePurgeContextList(jitSource, relationId); + + // we also need to purge deprecate list + MotJitContext* itr = jitSource->m_deprecateContextList; + while (itr != nullptr) { + PurgeJitContext(itr, relationId); + itr = itr->m_nextDeprecate; + } + (void)pthread_mutex_unlock(&jitSource->m_lock); +} + +extern void DeprecateJitSource(JitSource* jitSource, bool markOnly) +{ + MotJitContext* recyclableContext = nullptr; + (void)pthread_mutex_lock(&jitSource->m_lock); + MOT_LOG_TRACE("Trying to mark JIT source %p with status %s as deprecated: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + + // if someone is revalidating right now, we avoid deprecating the source, since revalidation might take place + // before we even clone the context (so the use count was not incremented yet) + if (!markOnly) { + if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + if (jitSource->m_sourceJitContext != nullptr) { + jitSource->m_sourceJitContext->m_jitSource = nullptr; // avoid warning + DeprecateSourceJitContext(jitSource, &recyclableContext); + } + } + JitSourceDeprecateContextList(jitSource); + } + if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + MOT_ASSERT(jitSource->m_deprecatedPendingCompile == 0); + // If we have raised the m_deprecatedPendingCompile flag and state was changed to + // JitCodegenState::JIT_CODEGEN_DEPRECATE already by another session, we don't have to change it again. + if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_DEPRECATE) { + MOT_LOG_TRACE("Marking JIT source %p with status %s (deprecatedPendingCompile %u) as deprecate: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_deprecatedPendingCompile, + jitSource->m_queryString); + jitSource->m_codegenState = JitCodegenState::JIT_CODEGEN_DEPRECATE; + jitSource->m_timestamp = GetCurrentTimestamp(); + } + } else { + // We raise the m_deprecatedPendingCompile flag if it was not raised already. + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (jitSource->m_deprecatedPendingCompile == 0)) { + // Someone is still compiling. We avoid deprecating the source, but still put it in the deprecated list. + // Once that compilation is finished, the status will be changed to JitCodegenState::JIT_CODEGEN_DEPRECATE + // and then the source can be freed. + MOT_LOG_TRACE("Marking JIT source %p with status unavailable as deprecate pending compile: %s", + jitSource, + jitSource->m_queryString); + jitSource->m_deprecatedPendingCompile = 1; + } + } + (void)pthread_mutex_unlock(&jitSource->m_lock); + + if (recyclableContext != nullptr) { + DestroyJitContext(recyclableContext); + recyclableContext = nullptr; + } +} + +extern const char* JitSourceStatusToString(JitSourceStatus status) +{ + switch (status) { + case JitSourceStatus::JIT_SOURCE_INVALID: + return "invalid"; + + case JitSourceStatus::JIT_SOURCE_JITTABLE: + return "jittable"; + + case JitSourceStatus::JIT_SOURCE_UNJITTABLE: + return "unjittable"; + + default: + return "N/A"; + } +} + +extern JitSource* CloneLocalJitSource(JitSource* jitSource, bool cloneContext, JitSourceOp sourceOp) +{ + JitSource* localSource = AllocJitSource(jitSource->m_queryString, JIT_CONTEXT_LOCAL); + if (localSource == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Process DDL event", "Failed to allocate pooled session-local JIT source"); + return nullptr; + } + MOT_LOG_TRACE( + "Created jit-source object %p during clone-local-source: %s", localSource, localSource->m_queryString); + + (void)pthread_mutex_lock(&jitSource->m_lock); + // move current session-local context objects from global source to local source (isolating them from outer noise) + bool copySuccess = CopyJitSource(jitSource, localSource, cloneContext, JIT_CONTEXT_LOCAL, sourceOp); + (void)pthread_mutex_unlock(&jitSource->m_lock); + + if (!copySuccess) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Process DDL event", "Failed to copy JIT source"); + DestroyJitSource(localSource); + localSource = nullptr; + } + return localSource; +} + +extern JitSource* CloneGlobalJitSource(JitSource* jitSource) +{ + JitSource* globalSource = AllocJitSource(jitSource->m_queryString, JIT_CONTEXT_GLOBAL); + if (globalSource == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Process DDL event", "Failed to allocate pooled global JIT source"); + return nullptr; + } + + MOT_LOG_TRACE( + "Created jit-source object %p during clone-global-source: %s", globalSource, globalSource->m_queryString); + + // fill in basic details and then just merge back + globalSource->m_commandType = jitSource->m_commandType; + globalSource->m_contextType = jitSource->m_contextType; + globalSource->m_functionOid = jitSource->m_functionOid; + globalSource->m_functionTxnId = jitSource->m_functionTxnId; + globalSource->m_expireTxnId = jitSource->m_expireTxnId; + + JitContextList rollbackInvokeContextList; + MergeJitSource(jitSource, globalSource, true, &rollbackInvokeContextList); + MOT_ASSERT(rollbackInvokeContextList.Empty()); + return globalSource; +} + +extern void MergeJitSource( + JitSource* localSource, JitSource* globalSource, bool applyLocalChanges, JitContextList* rollbackInvokeContextList) +{ + MOT_LOG_TRACE("Merging local JIT source %p with state %s into global JIT source %p: %s", + localSource, + JitCodegenStateToString(localSource->m_codegenState), + globalSource, + localSource->m_queryString); + + bool isInvokeQuery = IsInvokeQuerySource(localSource); + + // in case of commit we want to preserve the timestamp + TimestampTz lastChangeTimestamp = localSource->m_timestamp; + + // in case of global publish (commit use case), we first invalidate all other session's contexts (because they need + // to get new jitted function and relation ids and pointers) + (void)pthread_mutex_lock(&globalSource->m_lock); + if (applyLocalChanges) { + // we first expire all contexts (also session local because they need to use a different table pointer) + SetJitSourceExpired(globalSource, 0, (localSource->m_codegenState == JitCodegenState::JIT_CODEGEN_REPLACED)); + } + + // then we put back all JIT context objects and invalidate them as well (whether commit occurred or rollback) + if (localSource->m_contextList != nullptr) { + MotJitContext* currItr = localSource->m_contextList; + MotJitContext* nextItr = nullptr; + while (currItr != nullptr) { + MOT_LOG_TRACE("Moving back JIT context %p from local source %p to global source %p", + currItr, + localSource, + globalSource); + + // update lists + nextItr = currItr->m_nextInSource; + currItr->m_nextInSource = globalSource->m_contextList; + globalSource->m_contextList = currItr; + currItr->m_jitSource = globalSource; + + // update fields + currItr->m_queryString = globalSource->m_queryString; + if (currItr->m_sourceJitContext) { + MOT_ATOMIC_DEC(currItr->m_sourceJitContext->m_useCount); + MOT_LOG_TRACE("MergeJitSource(): Decremented use count of source context %p to: %u", + currItr->m_sourceJitContext, + (uint32_t)MOT_ATOMIC_LOAD(currItr->m_sourceJitContext->m_useCount)); + } + currItr->m_sourceJitContext = globalSource->m_sourceJitContext; + if (currItr->m_sourceJitContext) { + MOT_ATOMIC_INC(currItr->m_sourceJitContext->m_useCount); + MOT_LOG_TRACE("MergeJitSource(): Incremented use count of source context %p to: %u", + currItr->m_sourceJitContext, + (uint32_t)MOT_ATOMIC_LOAD(currItr->m_sourceJitContext->m_useCount)); + } + + // in case of commit we need to re-fetch tables so we purge + PurgeJitContext(currItr, 0); // no relation id means unconditional purge + if (!applyLocalChanges || !IsJitContextUsageGlobal(currItr->m_usage)) { + // in case of roll-back we need to rebuild everything from scratch for current session contexts + InvalidateJitContext(currItr, 0, JIT_CONTEXT_INVALID); // no relation id means unconditional purge + // we also need to make sure that the cached plan pointer is in-place (it might be remove during + // transactional revalidation) + } + + if (!applyLocalChanges && isInvokeQuery) { + // In case of rollback and if this is a invoke query, we must remove the attached invoke context and + // destroy it (done in the caller) before the local function source is deprecated. + // Otherwise, deprecation of local function source will fail. + JitQueryContext* queryContext = (JitQueryContext*)currItr; + if (queryContext->m_invokeContext != nullptr) { + rollbackInvokeContextList->PushBack(queryContext->m_invokeContext); + queryContext->m_invokeContext = nullptr; + } + } + currItr = nextItr; + } + localSource->m_contextList = nullptr; + // retain state if committing non-ready source + if (applyLocalChanges && (localSource->m_codegenState != JitCodegenState::JIT_CODEGEN_READY)) { + globalSource->m_codegenState = localSource->m_codegenState; + globalSource->m_status = localSource->m_status; + } + } + + // in case of global publish (commit use case), we install the locally produced context if there is any + if (applyLocalChanges && (localSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY)) { + MOT_ASSERT(localSource->m_sourceJitContext != nullptr); + MOT_ASSERT(globalSource->m_sourceJitContext == nullptr); + MotJitContext* readyJitContext = localSource->m_sourceJitContext; + localSource->m_sourceJitContext = nullptr; + if (readyJitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + PurgeJitContext(readyJitContext, 0); // force re-fetch even for source context + } + + // install new context in global source + (void)SetJitSourceReady(globalSource, readyJitContext, &localSource->m_codegenStats); + + // Attention: although table/index ids are valid (even after drop and create), new table pointers have not been + // set yet in the table manager. When first session revalidates we can re-fetch table pointers, so we mark the + // source context is invalid due to table pointers. + if (globalSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + MOT_ATOMIC_STORE(globalSource->m_sourceJitContext->m_validState, JIT_CONTEXT_RELATION_INVALID); + } + // override artificial changes in timestamp + globalSource->m_timestamp = lastChangeTimestamp; + } + (void)pthread_mutex_unlock(&globalSource->m_lock); +} + +extern void MoveCurrentSessionContexts(JitSource* source, JitSource* target, JitSourceOp sourceOp) +{ + MOT::SessionId currSessionId = MOT_GET_CURRENT_SESSION_ID(); + if (source->m_contextList != nullptr) { + MotJitContext* prevItr = nullptr; + MotJitContext* currItr = source->m_contextList; + while (currItr != nullptr) { + if ((currItr->m_usage == JIT_CONTEXT_LOCAL) && (currItr->m_sessionId == currSessionId)) { + MOT_LOG_TRACE("Moving JIT context %p from source %p to source %p", currItr, source, target); + + // every moved context must be invalidated, since local session just received DDL event + if (sourceOp == JitSourceOp::JIT_SOURCE_PURGE_ONLY) { + PurgeJitContext(currItr, 0); + } else if (sourceOp == JitSourceOp::JIT_SOURCE_INVALIDATE) { + PurgeJitContext(currItr, 0); + InvalidateJitContext(currItr, 0, JIT_CONTEXT_INVALID); + } + + // update fields + currItr->m_jitSource = target; + if (currItr->m_sourceJitContext) { + MOT_ATOMIC_DEC(currItr->m_sourceJitContext->m_useCount); + MOT_LOG_TRACE("MoveCurrentSessionContexts(): Decremented use count of source context %p to: %u", + currItr->m_sourceJitContext, + (uint32_t)MOT_ATOMIC_LOAD(currItr->m_sourceJitContext->m_useCount)); + } + currItr->m_sourceJitContext = target->m_sourceJitContext; + if (currItr->m_sourceJitContext) { + MOT_ATOMIC_INC(currItr->m_sourceJitContext->m_useCount); + MOT_LOG_TRACE("MoveCurrentSessionContexts(): Incremented use count of source context %p to: %u", + currItr->m_sourceJitContext, + (uint32_t)MOT_ATOMIC_LOAD(currItr->m_sourceJitContext->m_useCount)); + } + currItr->m_queryString = target->m_queryString; + + // fix lists + if (prevItr == nullptr) { // unlink head + source->m_contextList = currItr->m_nextInSource; + currItr->m_nextInSource = target->m_contextList; + target->m_contextList = currItr; + currItr = source->m_contextList; + } else { + prevItr->m_nextInSource = currItr->m_nextInSource; + currItr->m_nextInSource = target->m_contextList; + target->m_contextList = currItr; + currItr = prevItr->m_nextInSource; + } + } else { // iterate to next context + prevItr = currItr; + currItr = currItr->m_nextInSource; + } + } + } +} + +extern bool CleanUpDeprecateJitSourceContexts(JitSource* source) +{ + bool result = false; + JitContextList deprecatedContexts; + LockJitSource(source); + if (source->m_deprecateContextList != nullptr) { + MOT_LOG_TRACE("Cleaning up deprecate JIT context objects in JIT source %p", source); + uint32_t contextsRemoved = 0; + MotJitContext* itr = source->m_deprecateContextList; + MotJitContext* prev = nullptr; + MotJitContext* next = nullptr; + while (itr != nullptr) { + next = itr->m_nextDeprecate; + if (MOT_ATOMIC_LOAD(itr->m_useCount) == 0) { + if (prev == nullptr) { // unlinking head + MOT_ASSERT(source->m_deprecateContextList == itr); + source->m_deprecateContextList = itr->m_nextDeprecate; + } else { + prev->m_nextDeprecate = itr->m_nextDeprecate; + } + itr->m_jitSource = nullptr; // avoid warning + MOT_LOG_TRACE("Removed deprecate JIT context %p in source %p: %s", itr, source, source->m_queryString); + deprecatedContexts.PushBack(itr); + ++contextsRemoved; + } else { + prev = itr; + } + itr = next; + } + MOT_LOG_TRACE("Removed %u deprecate source context objects in JIT source %p", contextsRemoved, source); + } + if ((source->m_deprecateContextList == nullptr) && (source->m_contextList == nullptr)) { + result = true; + } + UnlockJitSource(source); + + MotJitContext* contextItr = deprecatedContexts.Begin(); + while (contextItr != nullptr) { + MotJitContext* jitContext = contextItr; + contextItr = contextItr->m_next; + jitContext->m_next = nullptr; + MOT_LOG_TRACE("Destroying deprecate JIT source context %p", jitContext); + DestroyJitContext(jitContext); } return result; } -extern void PurgeJitSource(JitSource* jitSource, uint64_t relationId) -{ - pthread_mutex_lock(&jitSource->_lock); - PurgeJitContext(jitSource->_source_jit_context, relationId); - JitSourcePurgeContextList(jitSource, relationId); - pthread_mutex_unlock(&jitSource->_lock); -} - static void JitSourcePurgeContextList(JitSource* jitSource, uint64_t relationId) { MOT_LOG_TRACE("Purging all JIT context objects by relation id %" PRIu64, relationId); - JitContext* itr = jitSource->m_contextList; + MotJitContext* itr = jitSource->m_contextList; while (itr != nullptr) { PurgeJitContext(itr, relationId); itr = itr->m_nextInSource; } } -static void RemoveJitSourceContextImpl(JitSource* jitSource, JitContext* jitContext) +static void JitSourceInformCompileEvent(JitSource* jitSource, JitSourceList* parentSourceList, uint8_t event) { - MOT_LOG_TRACE("Removing JIT context %p from source %p", jitContext, jitSource); - JitContext* prev = nullptr; - JitContext* curr = jitSource->m_contextList; + MotJitContext* itr = jitSource->m_contextList; + while (itr != nullptr) { + if (event == JIT_CONTEXT_DONE_COMPILE) { + if (IsJitContextPendingCompile(itr)) { + MarkJitContextDoneCompile(itr); + } + } else { + MarkJitContextErrorCompile(itr); + } + + // special use case: this is a JIT SP source, so we want to inform also other invoke queries that use this SP + if ((parentSourceList != nullptr) && (itr->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION)) { + MotJitContext* parentContext = itr->m_parentContext; + if (parentContext != nullptr) { + JitSource* parentSource = parentContext->m_jitSource; + if (parentSource != nullptr) { + parentSourceList->PushBack(parentSource); + } + } + } + MOT_ASSERT(itr->m_contextType == jitSource->m_contextType); + itr = itr->m_nextInSource; + } +} + +static void JitSourceListInformCompileEvent(JitSourceList* sourceList, uint8_t event) +{ + JitSource* itr = sourceList->Begin(); + while (itr != nullptr) { + JitSource* jitSource = itr; + itr = itr->m_next; + jitSource->m_next = nullptr; + LockJitSource(jitSource); + if (jitSource->m_pendingChildCompile == JIT_CONTEXT_PENDING_COMPILE) { + if (event == JIT_CONTEXT_DONE_COMPILE) { + // trigger attempt to revalidate on next invocation + jitSource->m_pendingChildCompile = JIT_CONTEXT_DONE_COMPILE; + JitSourceInformCompileDone(jitSource); + } else { + // trigger attempt to revalidate on next invocation + jitSource->m_pendingChildCompile = JIT_CONTEXT_ERROR_COMPILE; + JitSourceInformCompileError(jitSource); + } + } + UnlockJitSource(jitSource); + } +} + +static void JitSourceInformCompileDone(JitSource* jitSource, JitSourceList* parentSourceList /* = nullptr */) +{ + MOT_LOG_TRACE("Informing all JIT context objects compilation is done: %s", jitSource->m_queryString); + JitSourceInformCompileEvent(jitSource, parentSourceList, JIT_CONTEXT_DONE_COMPILE); +} + +static void JitSourceInformCompileError(JitSource* jitSource, JitSourceList* parentSourceList /* = nullptr */) +{ + MOT_LOG_TRACE("Informing all JIT context objects compilation finished with error: %s", jitSource->m_queryString); + JitSourceInformCompileEvent(jitSource, parentSourceList, JIT_CONTEXT_ERROR_COMPILE); +} + +static void JitSourceListInformCompileDone(JitSourceList* sourceList, const char* queryString) +{ + MOT_LOG_TRACE("Informing all JIT source parents compilation is done: %s", queryString); + JitSourceListInformCompileEvent(sourceList, JIT_CONTEXT_DONE_COMPILE); +} + +static void JitSourceListInformCompileError(JitSourceList* sourceList, const char* queryString) +{ + MOT_LOG_TRACE("Informing all JIT source parents compilation is finished with error: %s", queryString); + JitSourceListInformCompileEvent(sourceList, JIT_CONTEXT_ERROR_COMPILE); +} + +static void JitSourceInvalidateContextList(JitSource* jitSource, uint64_t relationId) +{ + MOT_LOG_TRACE("Invalidating all JIT context objects related to source"); + MotJitContext* itr = jitSource->m_contextList; + while (itr != nullptr) { + InvalidateJitContext(itr, relationId); + itr = itr->m_nextInSource; + } +} + +static void JitSourceDeprecateContextList(JitSource* jitSource) +{ + MOT_LOG_TRACE("Deprecating all JIT context objects related to JIT source %p", jitSource); + MotJitContext* itr = jitSource->m_contextList; + while (itr != nullptr) { + DeprecateJitContext(itr); + itr = itr->m_nextInSource; + } +} + +static void RemoveJitSourceContextImpl(JitSource* jitSource, MotJitContext* jitContext) +{ + MOT_ASSERT(jitContext->m_jitSource == jitSource); + MOT_LOG_TRACE("Removing JIT context %p from source %p/%p", jitContext, jitSource, jitContext->m_sourceJitContext); + MotJitContext* prev = nullptr; + MotJitContext* curr = jitSource->m_contextList; while ((curr != nullptr) && (jitContext != curr)) { prev = curr; curr = curr->m_nextInSource; } - if (curr != nullptr) { + if (curr == jitContext) { MOT_LOG_TRACE("JIT context %p found in source %p, now removing", jitContext, jitSource); if (prev != nullptr) { + MOT_ASSERT(prev->m_nextInSource == jitContext); prev->m_nextInSource = curr->m_nextInSource; } else { // curr is head MOT_ASSERT(curr == jitSource->m_contextList); @@ -399,32 +2222,174 @@ static void RemoveJitSourceContextImpl(JitSource* jitSource, JitContext* jitCont } } else { MOT_LOG_WARN("Cannot remove JIT context %p from source %p: context not found", jitContext, jitSource); + MOT_ASSERT(false); + } +} + +/* + * NOTE: If the source context can be recycled, it is returned in the output parameter (recyclableContext). + * Caller's responsibility to free recyclableContext if it is not NULL, since it should be done outside the + * JIT source lock. Otherwise, it could lead to deadlock when in RemoveJitSourceContext() when trying to + * ScheduleDeprecateJitSourceCleanUp(). + */ +static void DeprecateSourceJitContext(JitSource* jitSource, MotJitContext** recyclableContext) +{ + MotJitContext* sourceContext = jitSource->m_sourceJitContext; + *recyclableContext = nullptr; + MOT_LOG_TRACE( + "Deprecating source JIT context %p of JIT source %p: %s", sourceContext, jitSource, jitSource->m_queryString); + if (MOT_ATOMIC_LOAD(sourceContext->m_useCount) == 0) { + // We can safely destroy the context, but it should be done outside the JIT source lock. Otherwise, it could + // lead to deadlock when in RemoveJitSourceContext() when trying to ScheduleDeprecateJitSourceCleanUp(). + // So we fill the output parameter and let the caller destroy the context. + MOT_LOG_TRACE("Source JIT context %p is ready for recycling: %s", sourceContext, jitSource->m_queryString); + *recyclableContext = sourceContext; + } else { + MOT_ATOMIC_STORE(sourceContext->m_validState, JIT_CONTEXT_DEPRECATE); + MOT_ASSERT(!DeprecateListContainsJitContext(jitSource, sourceContext)); + sourceContext->m_nextDeprecate = jitSource->m_deprecateContextList; + jitSource->m_deprecateContextList = sourceContext; + MOT_LOG_TRACE("Source JIT context %p is pushed to JIT source %p deprecated context list: %s", + sourceContext, + jitSource, + jitSource->m_queryString); + } + jitSource->m_sourceJitContext = nullptr; +} + +static void UnlinkDeprecateJitContext(JitSource* jitSource, MotJitContext* sourceJitContext) +{ + MOT_ASSERT(MOT_ATOMIC_LOAD(sourceJitContext->m_useCount) == 0); + MOT_LOG_TRACE("Unlinking deprecate JIT source %p source context %p: %s", + jitSource, + sourceJitContext, + jitSource->m_queryString); + + // unlink deprecate source from deprecate list and destroy it + // NOTE: at this point we are already locked when lock is needed (see RevalidateJitContext() calling + // ReplenishJitContext(), which calls this function) + MotJitContext* itr = jitSource->m_deprecateContextList; + MotJitContext* prev = nullptr; + while ((itr != nullptr) && (itr != sourceJitContext)) { + prev = itr; + itr = itr->m_nextDeprecate; + } + if (itr == nullptr) { + MOT_LOG_TRACE("JIT context %p not found in deprecate list of JIT source %p: %s", + sourceJitContext, + jitSource, + jitSource->m_queryString); + MOT_ASSERT(false); + } else { + MOT_ASSERT(itr == sourceJitContext); + if (prev == nullptr) { // un-linking head + MOT_ASSERT(jitSource->m_deprecateContextList == sourceJitContext); + jitSource->m_deprecateContextList = sourceJitContext->m_nextDeprecate; + } else { + prev->m_nextDeprecate = sourceJitContext->m_nextDeprecate; + } + sourceJitContext->m_jitSource = nullptr; // avoid warning + MOT_LOG_TRACE("Unlinked deprecate JIT source context %p in source %p: %s", + sourceJitContext, + jitSource, + jitSource->m_queryString); + } +} + +extern void UnlinkLocalJitSourceContexts(JitSource* localSource) +{ + MOT_ASSERT(localSource->m_usage == JIT_CONTEXT_LOCAL); + MOT_LOG_DEBUG( + "Unlinking all contexts of local JIT source %p with query string: %s", localSource, localSource->m_queryString); + + if (localSource->m_contextList != nullptr) { + MotJitContext* itr = localSource->m_contextList; + while (itr != nullptr) { + MotJitContext* cleanupContext = itr; + itr = itr->m_nextInSource; + + MOT_LOG_TRACE("Unlinking JIT context %p from local JIT source %p with query string: %s", + cleanupContext, + localSource, + localSource->m_queryString); + + // Purge and invalidate the context. + PurgeJitContext(cleanupContext, 0); + InvalidateJitContext(cleanupContext, 0, JIT_CONTEXT_INVALID); + + // Unlink from the source. + MotJitContext* sourceContext = cleanupContext->m_sourceJitContext; + if (sourceContext != nullptr) { + MOT_ATOMIC_DEC(sourceContext->m_useCount); + MOT_LOG_TRACE("CleanupLocalJitSourceContexts(): Decremented use count of source context %p to: %u", + sourceContext, + (uint32_t)MOT_ATOMIC_LOAD(sourceContext->m_useCount)); + cleanupContext->m_sourceJitContext = nullptr; + } + cleanupContext->m_jitSource = nullptr; + cleanupContext->m_nextInSource = nullptr; + } + localSource->m_contextList = nullptr; } } #ifdef MOT_DEBUG -static void VerifyJitSourceStateTrans(JitSource* jitSource, JitContext* readySourceJitContext, JitContextStatus status) +static bool DeprecateListContainsJitContext(JitSource* jitSource, MotJitContext* jitContext) +{ + MotJitContext* itr = jitSource->m_deprecateContextList; + while ((itr != nullptr) && (itr != jitContext)) { + itr = itr->m_nextDeprecate; + } + return (itr == jitContext); +} + +static bool ContextListContainsJitContext(JitSource* jitSource, MotJitContext* jitContext) +{ + MotJitContext* itr = jitSource->m_contextList; + while ((itr != nullptr) && (itr != jitContext)) { + itr = itr->m_nextInSource; + } + return (itr == jitContext); +} + +static void VerifyJitSourceStateTrans( + JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenState nextState) { // first verify JIT source state MOT_ASSERT(jitSource != nullptr); - if (jitSource->_status == JIT_CONTEXT_READY) { - MOT_ASSERT(jitSource->_source_jit_context != nullptr); - } else { - MOT_ASSERT(jitSource->_source_jit_context == nullptr); + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + MOT_ASSERT(jitSource->m_sourceJitContext != nullptr); + } else if (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE && + jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_PENDING) { + // unavailable state might have a JIT context when re-validating + MOT_ASSERT(jitSource->m_sourceJitContext == nullptr); } // next verify target state - if (status == JIT_CONTEXT_READY) { + if (nextState == JitCodegenState::JIT_CODEGEN_READY) { MOT_ASSERT(readySourceJitContext != nullptr); } else { MOT_ASSERT(readySourceJitContext == nullptr); } // now verify state transition - if (jitSource->_status == JIT_CONTEXT_UNAVAILABLE) { - MOT_ASSERT((status == JIT_CONTEXT_READY) || (status == JIT_CONTEXT_ERROR)); - } else { - MOT_ASSERT(status == JIT_CONTEXT_EXPIRED); + if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE) { + MOT_ASSERT((nextState == JitCodegenState::JIT_CODEGEN_READY) || + (nextState == JitCodegenState::JIT_CODEGEN_ERROR) || + (nextState == JitCodegenState::JIT_CODEGEN_EXPIRED)); + } else if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + MOT_ASSERT((nextState == JitCodegenState::JIT_CODEGEN_EXPIRED) || + (nextState == JitCodegenState::JIT_CODEGEN_DROPPED) || + (nextState == JitCodegenState::JIT_CODEGEN_ERROR)); + } else if (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_EXPIRED) { + MOT_ASSERT((nextState == JitCodegenState::JIT_CODEGEN_READY) || + (nextState == JitCodegenState::JIT_CODEGEN_ERROR) || + (nextState == JitCodegenState::JIT_CODEGEN_EXPIRED)); + } else if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) || + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_REPLACED)) { + MOT_ASSERT((nextState == JitCodegenState::JIT_CODEGEN_READY) || + (nextState == JitCodegenState::JIT_CODEGEN_ERROR) || + (nextState == JitCodegenState::JIT_CODEGEN_EXPIRED)); } } #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source.h b/src/gausskernel/storage/mot/jit_exec/jit_source.h index 533c48414..128545bfd 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_source.h @@ -28,25 +28,76 @@ #include #include "jit_context.h" +#include "pgstat.h" +#include "jit_common.h" +#include "jit_plan.h" +#include "mot_list.h" namespace JitExec { + /** - * @enum Constant values denoting return status for @ref WaitJitContextReady(). */ -enum JitContextStatus : uint32_t { - /** @var Source context is ready for use. */ - JIT_CONTEXT_READY, + * @enum Constant values denoting code-generation state of the JIT source.. */ +enum class JitCodegenState : uint8_t { + /** @var Generated code is ready for use (the source context can be cloned). */ + JIT_CODEGEN_READY, - /** @var Source context is unavailable yet (it is being generated at the moment). */ - JIT_CONTEXT_UNAVAILABLE, + /** @var Generated code is unavailable yet (code is being generated at the moment). */ + JIT_CODEGEN_UNAVAILABLE, - /** @var Source context generation failed. This query will never be JIT-ted again. */ - JIT_CONTEXT_ERROR, + /** @var Code generation failed. This query will not be JIT-ted again until invalidation. */ + JIT_CODEGEN_ERROR, /** - * @var The source context expired since a DDL caused this query to be re-evaluated. Caller is required to - * generate a new context. Only one session sees this status. + * @var The generated code expired since a DDL caused this query to be invalidated. Caller is required to + * re-generate code for the query. This is a temporary state, and the first session to revalidate the JIT source + * will atomically change state to unavailable. */ - JIT_CONTEXT_EXPIRED + JIT_CODEGEN_EXPIRED, + + /** @var The generated code is no longer usable, since the stored procedure was dropped. */ + JIT_CODEGEN_DROPPED, + + /** @var The generated code is temporarily unusable, since the stored procedure is about to be replaced. */ + JIT_CODEGEN_REPLACED, + + /** @var The generated code is deprecate. */ + JIT_CODEGEN_DEPRECATE, + + /** @var The invoked query is pending an invoked stored procedure to finish compilation. */ + JIT_CODEGEN_PENDING, + + /** @brief Code for non-jittable sub-query. */ + JIT_CODEGEN_NONE +}; + +/** @enum Constants denoting jittable status. */ +enum class JitSourceStatus : uint8_t { + /** @var Denotes JIT source is in invalid state (temporary state after invalidation due to DDL). */ + JIT_SOURCE_INVALID, + + /** @var Denotes JIT source is in jittable state. */ + JIT_SOURCE_JITTABLE, + + /** @var Denotes JIT source is in unjittable state. */ + JIT_SOURCE_UNJITTABLE +}; + +/** @enum Constants denoting source operation. */ +enum class JitSourceOp { + /** @var No special action should be taken during clone. */ + JIT_SOURCE_NO_OP, + + /** @var Purge all moved contexts. */ + JIT_SOURCE_PURGE_ONLY, + + /** @var Purge and invalidate all moved contexts. */ + JIT_SOURCE_INVALIDATE, + + /** @var Mark functions replaced for all moved contexts. */ + JIT_SOURCE_REPLACE_FUNCTION, + + /** @var Mark functions dropped for all moved contexts. */ + JIT_SOURCE_DROP_FUNCTION }; /** @@ -55,44 +106,165 @@ enum JitContextStatus : uint32_t { */ struct __attribute__((packed)) JitSource { /** @var Lock used for concurrent compilation. */ - pthread_mutex_t _lock; // L1 offset 0 (40 bytes) + pthread_mutex_t m_lock; // L1 offset 0 (40 bytes) /** @var Keep noisy lock in its own cache line. */ - uint8_t _padding1[24]; // L1 offset 40 - - /** @var Condition variable used to notify other threads waiting for compiler thread to finish. */ - pthread_cond_t _cond; // L1 offset 0 (48 bytes) - - /** @var Keep noisy condition variable in its own cache line. */ - uint8_t _padding2[16]; // L1 offset 48 + uint8_t m_padding1[24]; // L1 offset 40 /** @var The compiled function. */ - JitContext* _source_jit_context; // L1 offset 0 + MotJitContext* m_sourceJitContext; // L1 offset 0 /** @var Tells whether the object was initialized successfully. */ - uint32_t _initialized; // L1 offset 8 + uint8_t m_initialized; // L1 offset 8 - /** @var The status of the context. */ - JitContextStatus _status; // L1 offset 12 + /** @var Specifies whether this JIT source is pending for child compilation to finish. */ + uint8_t m_pendingChildCompile; // L1 offset 9 + + /** @var The code generation state of the context. */ + JitCodegenState m_codegenState; // L1 offset 10 + + /** @var The jittable state of the context. */ + JitSourceStatus m_status; // L1 offset 11 + + /** @var The type of context being managed. */ + JitContextType m_contextType; // L1 offset 12 + + /** @var Specified the command type of the contained JIT context*/ + JitCommandType m_commandType; // L1 offset 13 + + /** @var JIT source usage. */ + JitContextUsage m_usage; // L1 offset 14 + + /** + * @var Flag to indicate that the source was dropped concurrently during compilation. + * This source should be deprecated once the compilation finishes. + */ + uint8_t m_deprecatedPendingCompile; // L1 offset 15 /** @var Manage a list of cached context source objects for each session. */ - JitSource* _next; // L1 offset 16 + JitSource* m_next; // L1 offset 16 /** @var The source query string. */ - char* _query_string; // L1 offset 24 + char* m_queryString; // L1 offset 24 - /** @var Jit context list for cleanup during relation modification. */ - JitContext* m_contextList; // L1 offset 32 + /** @var JIT context list for cleanup during relation modification. */ + MotJitContext* m_contextList; // L1 offset 32 + + /** @var Time of last change. */ + TimestampTz m_timestamp; // L1 offset 40 + + /** @var The function id in case of a function or an invoke query source. */ + Oid m_functionOid; // L1 offset 48 + + /** @var The table id in case of a query source. */ + uint32_t m_tableId; // L1 offset 52 + + /** @var The inner table id in case of a join query source. */ + uint32_t m_innerTableId; // L1 offset 56 + + /** @var Unique identifier of the source in the source pool. */ + uint32_t m_sourceId; // L1 offset 60 + + /** @var Code generation statistics. */ + JitCodegenStats m_codegenStats; // L1 offset 0 + + /** @var JIT context list for delaying source context destruction until safe point in time. */ + MotJitContext* m_deprecateContextList; // L1 offset 32 + + /** @var The function txn id in case of a function or an invoke query source. */ + TransactionId m_functionTxnId; // L1 offset 40 + + /** @var The Txn Id which expired this source (applicable only for function or an invoke query source). */ + TransactionId m_expireTxnId; // L1 offset 48 + + /** @var Manage a list of deprecated sources. */ + JitSource* m_nextDeprecate; // L1 offset 56 }; +/** @class A simple linked list of JIT source objects. */ +class JitSourceList { +public: + /** @brief Default constructor. */ + JitSourceList() : m_head(nullptr), m_tail(nullptr) + {} + + /** @brief Default destructor. */ + ~JitSourceList() + { + m_head = nullptr; + m_tail = nullptr; + } + + /** @brief Pushes an element on back of list. */ + inline void PushBack(JitSource* jitSource) + { + if (m_tail == nullptr) { + m_head = m_tail = jitSource; + } else { + m_tail->m_next = jitSource; + m_tail = jitSource; + } + jitSource->m_next = nullptr; + } + + /** @brief Pushes an element on back of list. */ + inline void PushBackDeprecate(JitSource* jitSource) + { + if (m_tail == nullptr) { + m_head = m_tail = jitSource; + } else { + m_tail->m_nextDeprecate = jitSource; + m_tail = jitSource; + } + jitSource->m_nextDeprecate = nullptr; + } + + /** @brief Starts iterating over list. */ + inline JitSource* Begin() + { + return m_head; + } + + /** @brief Queries whether list is empty. */ + inline bool Empty() const + { + return (m_head == nullptr); + } + +private: + /** @var The list head element. */ + JitSource* m_head; + + /** @var The list tail element. */ + JitSource* m_tail; +}; + +inline bool IsInvokeQuerySource(JitSource* jitSource) +{ + return ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType == JIT_COMMAND_INVOKE)); +} + +inline bool IsSimpleQuerySource(JitSource* jitSource) +{ + return ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType != JIT_COMMAND_INVOKE)); +} + +inline bool IsJitSourceRecyclable(JitSource* jitSource) +{ + return ((jitSource->m_contextList == nullptr) && (jitSource->m_deprecateContextList == nullptr) && + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DEPRECATE)); +} + /** * @brief Initializes a JIT source object. * @param jitSource The JIT source to initialize. * @param queryString The query string of the JIT source. - * @return True if initialization succeeded, otherwise false. Consult @ref mm_get_root_error() for - * further details. + * @param usage The JIT source usage (global, secondary global, or local) + * @return True if initialization succeeded, otherwise false. Consult @ref mm_get_root_error() for further details. */ -extern bool InitJitSource(JitSource* jitSource, const char* queryString); +extern bool InitJitSource(JitSource* jitSource, const char* queryString, JitContextUsage usage); /** * @brief Releases all resources associated with a JIT source. @@ -106,57 +278,128 @@ extern void DestroyJitSource(JitSource* jitSource); * reclaimed JIT source objects. Only partial initialization is required in this case. * @param jitSource The JIT source to re-initialize. * @param queryString The query string of the JIT source. + * @param usage Specifies the usage of the source. */ -extern void ReInitJitSource(JitSource* jitSource, const char* queryString); +extern void ReInitJitSource(JitSource* jitSource, const char* queryString, JitContextUsage usage); + +/** @brief Locks the JIT source for concurrent access. */ +extern void LockJitSource(JitSource* jitSource); + +/** @brief Unlocks the JIT source for concurrent access. */ +extern void UnlockJitSource(JitSource* jitSource); /** - * @brief Waits until the compiled function is ready for use (blocking call). + * @brief Gets a valid and ready to use JIT context from the JIT source. If the JIT source is ready, then its + * contained context will be cloned, otherwise only current state is returned without any cloning. * @param jitSource The JIT source to wait upon. * @param[out] readySourceJitContext The resulting ready JIT context (already cloned). + * @param usage The required resulting context usage. + * @param jitPlan The JIT plan * @return The resulting context status. */ -extern JitContextStatus WaitJitContextReady(JitSource* jitSource, JitContext** readySourceJitContext); +extern JitCodegenState GetReadyJitContext( + JitSource* jitSource, MotJitContext** readySourceJitContext, JitContextUsage usage, JitPlan* jitPlan); + +/** @brief Queries whether a JIT source is ready. */ +extern bool IsJitSourceReady(JitSource* jitSource); + +/** @brief Retrieves the valid state of the contained JIT context. */ +extern int GetJitSourceValidState(JitSource* jitSource); + +/** + * @brief Re-validates a JIT source after DDL that caused invalidation. In case if another thread is already + * validating the source, then unavailable state will be returned. + * @param jitSource The JIT source to re-validate. + * @param[optional] functionTxnId The function txn id in case of a function or an invoke query source. + * @return The resulting context status. + */ +extern JitCodegenState RevalidateJitSource(JitSource* jitSource, TransactionId functionTxnId = InvalidTransactionId); /** * @brief Sets the cached context status to error. * jitSource The JIT source to set its error status. * @param errorCode The error code of the root cause for the failure. + * @param[out,optional] newState On return specifies the current JIT source state (usually error as specified, but on + * race conditions could be deprecate). */ -extern void SetJitSourceError(JitSource* jitSource, int errorCode); +extern void SetJitSourceError(JitSource* jitSource, int errorCode, JitCodegenState* newState = nullptr); /** * @brief Sets the cached context status to error. * @param relationId The external identifier of the relation that caused this JIT source to expire. + * @param functionReplace Specifies whether this call was triggered by "REPLACE FUNCTION". */ -extern void SetJitSourceExpired(JitSource* jitSource, uint64_t relationId); +extern void SetJitSourceExpired(JitSource* jitSource, uint64_t relationId, bool functionReplace = false); /** * @brief Installs a ready source JIT context. - * jitSource The JIT source to set its ready status. + * @param jitSource The JIT source to set its ready status. * @param readySourceJitContext The ready source JIT context to install. + * @param codegenStats Code generation statistics. + * @param[out,optional] newState On return specifies the current JIT source state (usually ready as specified, but on + * race conditions could be deprecate). * @return True if operation succeeded, or false in case the JIT source is already ready with a valid JIT context. */ -extern bool SetJitSourceReady(JitSource* jitSource, JitContext* readySourceJitContext); +extern bool SetJitSourceReady(JitSource* jitSource, MotJitContext* readySourceJitContext, JitCodegenStats* codegenStats, + JitCodegenState* newState = nullptr); /** * @brief Registers JIT context for cleanup when its JIT source gets purged due to relation modification. * @param jitSource The originating JIT source. * @param jitContext The JIT context to register. */ -extern void AddJitSourceContext(JitSource* jitSource, JitContext* jitContext); +extern void AddJitSourceContext(JitSource* jitSource, MotJitContext* jitContext); /** * @brief Un-registers JIT context from cleanup when its JIT source gets purged due to relation modification. * @param jitSource The originating JIT source. - * @param jitContext The JIT context to un-register. + * @param cleanupContext The JIT context to un-register. */ -extern void RemoveJitSourceContext(JitSource* jitSource, JitContext* cleanupContext); +extern void RemoveJitSourceContext(JitSource* jitSource, MotJitContext* cleanupContext); /** @brief Queries whether a JIT source refers to a given relation. */ -extern bool JitSourceRefersRelation(JitSource* jitSource, uint64_t relationId); +extern bool JitSourceRefersRelation(JitSource* jitSource, uint64_t relationId, bool searchDeprecate); + +/** @brief Queries whether a JIT source refers to a given function. */ +extern bool JitSourceRefersSP(JitSource* jitSource, Oid functionId); + +/** @brief Retrieves the number of non-jittable sub-queries in this source. */ +extern uint32_t GetJitSourceNonJitQueryCount(JitSource* jitSource); /** @brief Purges the JIT source from all key/index references to the given relation. */ extern void PurgeJitSource(JitSource* jitSource, uint64_t relationId); + +/** @brief Marks the JIT source as deprecate. */ +extern void DeprecateJitSource(JitSource* jitSource, bool markOnly); + +/** @brief Retrieves a string representation of the current source code generation status. */ +extern const char* JitCodegenStateToString(JitCodegenState state); + +/** @brief Retrieves a string representation of the current source status. */ +extern const char* JitSourceStatusToString(JitSourceStatus status); + +/** @brief Make a session-local clone of the given JIT source. */ +extern JitSource* CloneLocalJitSource(JitSource* jitSource, bool cloneContext, JitSourceOp sourceOp); + +/** @brief Make a global clone of the given JIT source. */ +extern JitSource* CloneGlobalJitSource(JitSource* jitSource); + +/** @brief Merges a session-local JIT source its counterpart global source. */ +extern void MergeJitSource( + JitSource* localSource, JitSource* globalSource, bool applyLocalChanges, JitContextList* rollbackInvokeContextList); + +/** @brief Move current session-local context objects from global source to local source. */ +extern void MoveCurrentSessionContexts(JitSource* source, JitSource* target, JitSourceOp sourceOp); + +/** @brief Recycles deprecate source context that can be recycled. */ +extern bool CleanUpDeprecateJitSourceContexts(JitSource* source); + +/** @brief Queries whether this is a premature revalidation attempt on the given JIT source. */ +extern bool IsPrematureRevalidation(JitSource* jitSource, JitPlan* jitPlan); +extern bool IsPrematureRevalidation(JitSource* jitSource, TransactionId functionTxnId); + +/** @brief Unlinks all the session-local context objects from local source. */ +extern void UnlinkLocalJitSourceContexts(JitSource* localSource); } // namespace JitExec #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source_map.cpp b/src/gausskernel/storage/mot/jit_exec/jit_source_map.cpp index 9397d562f..0ff890715 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source_map.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_source_map.cpp @@ -25,135 +25,2091 @@ #include "global.h" #include "jit_source_map.h" #include "jit_source_pool.h" +#include "jit_common.h" #include "utilities.h" +#include "storage/mot/jit_exec.h" #include +#include + +// Global and Session-local JIT Source map for Correct Transactional Behavior: +// ========================================================================== +// 1. We use a session-local on-demand map for coping with session-local DDLs. The map is used as a restricted and +// modified view of the global map +// 2. As long as in the current transaction there was no DDL issued, we continue as usual to work with the global map. +// (see point 4 below). +// 3. As soon as the first DDL arrives, the following changes take place: +// A. A session-local map is created. +// B. All affected JIT QUERY sources are cloned to local map, taking with them all session-local JIT context objects. +// All cloned JIT sources are expired and notifications are sent to all related session-local JIT contexts. +// This is done only for sources not already found in local map (i.e. that were not previously cloned) +// C. All affected JIT SP query sources immediately cause for cloning of the entire SP tree, and invalidation of the +// related JIT SP Query source (in case it should be executed in the current transaction). +// D. Subsequent DDLs affecting local source will cause expire only in local source, and there is no need for +// cloning again the global source. +// 4. Any attempt to search for an existing jitted query/function is made first from the local map if such one exists. +// If it is not found in the local map, then we declare it is not found, because we cannot tell whether this +// query/function relies on some local DDL change (and so we cannot use the global source). If a local map does not +// exist (meaning no DDL was issued yet for the current transaction), then we search in the global map. +// 5. Any new jitted query/function is placed in the session-local map if such one exists, since we cannot tell whether +// it relies on any previous DDL for the current transaction. It will be published during commit (see next point). +// This includes all revalidation scenarios as well. +// 6. Once current transaction is committed, all changes in the local map are merged back to the global map as follows: +// A. All session-local contexts are merged back to global source (restoring the back pointer accordingly) +// B. All related contexts (including global ones) are purged to force re-fetching table/index objects in case of a +// valid LOCAL source +// C. All related contexts (including global ones) are invalidated to force full revalidation in case of an invalid +// LOCAL source +// 7. Once current transaction is rolled-back, all changes in the local map are discarded as follows: +// A. All session-local contexts are merged back to global source (restoring the back pointer accordingly) +// B. All related session-local contexts are purged to force re-fetching table/index objects in case of a valid +// GLOBAL source +// C. All related session-local contexts are invalidated to force full revalidation in case of an invalid GLOBAL +// source +// +// During first phase of implementation, jitted SPs are not taking part in transactional changes. +// Pay attention that all table/index pointer are now always taken from the current transaction, even during code- +// generation phase. namespace JitExec { DECLARE_LOGGER(JitSourceMap, JitExec); -// NOTE: Consider using oltp_map typedef std::map JitSourceMapType; +using JitNamespaceMapType = std::map; +using JitSPOidMapType = std::map; +using JitSourceSet = std::set; + +/** @enum Constants for stored procedure operations. */ +enum class JitSPOperation { + /** @var Denotes stored procedure replace operation. */ + JIT_SP_REPLACE, + + /** @var Denotes stored procedure drop operation. */ + JIT_SP_DROP +}; + +/** @typedef Maps of stored procedure operations (required for pruning). */ +using JitSPOperationMap = std::map; + +/** + * @const Format of Namespace is "%s.%u" (FunctionName.FunctionOid). Maximum length of function name is NAMEDATALEN. + * For simplicity, we use (NAMEDATALEN * 2) as maximum namespace length. + */ +static constexpr uint32_t JIT_MAX_NAMESPACE_LEN = NAMEDATALEN * 2; + +/** @struct JIT source name-space. */ +struct JitNamespace { + /** @var The identifier of the function inducing the name space.*/ + Oid m_functionId; + + /** @var The name-space. */ + char m_namespace[JIT_MAX_NAMESPACE_LEN]; + + /** @var The next name-space. */ + JitNamespace* m_next; +}; /** @struct Global JIT source map. */ -struct JitSourceMap { +struct JitGlobalSourceMap { +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK + /** @var Synchronize global map access. */ + pthread_rwlock_t m_rwlock; // 56 bytes + + /** @var Keep noisy lock in its own cache line. */ + uint8_t m_padding[8]; +#else /** @var Synchronize global map access. */ pthread_mutex_t m_lock; // 40 bytes /** @var Keep noisy lock in its own cache line. */ uint8_t m_padding[24]; +#endif /** @var Initialization flag. */ uint64_t m_initialized = 0; - /** @var The JIT source map. */ - JitSourceMapType m_sourceMap; + /** @var The JIT name space map. */ + JitNamespaceMapType m_namespaceMap; + + /** @var Synchronize deprecate sources access. */ + pthread_mutex_t m_deprecateLock; + + /** @var List of deprecate sources pending for recycling. */ + JitSourceSet m_deprecateSources; + + /** @var List of deprecate sources ready for recycling. */ + JitSourceSet m_readyDeprecateSources; + + /** @var Map to record the dropped SP Oid. */ + JitSPOidMapType m_droppedSPOidMap; }; -// Globals -static JitSourceMap g_jitSourceMap __attribute__((aligned(64))); +/** @struct Local JIT source map. */ +struct JitLocalSourceMap { + /** @var The JIT name space map. */ + JitNamespaceMapType m_namespaceMap; + + /** @var Records SP operations done during a transaction. */ + JitSPOperationMap m_operationMap; + + /** + * @var Records the new global sources SPs that are dropped during a transaction. + * These will be deprecated at the end of COMMIT. + */ + JitSourceMapType m_droppedSPMap; +}; + +// Global variables +static JitGlobalSourceMap g_jitSourceMap __attribute__((aligned(64))); + +// forward declarations +static bool AddCachedJitSource( + JitNamespaceMapType* namespaceMap, const char* queryNamespace, JitSource* cachedJitSource); +static JitSource* FindCachedJitSource( + const char* queryString, const char* queryNamespace, JitNamespaceMapType* namespaceMap); +static void ClearJitSourceMapImpl(JitNamespaceMapType* namespaceMap, bool issueWarnings, bool isShutdown); + +static void RestoreCachedPlanContexts(JitSource* jitSource); +static void PruneJitSourceMap(); // Not thread-safe +static void RemoveEmptyNamespaces(); // Not thread-safe +static void PurgeDeprecateQueryList(uint64_t relationId); +static void CleanUpReadyDeprecateJitSources(); +static void CleanUpDeprecateJitSources(); + +inline JitLocalSourceMap* GetLocalSourceMap() +{ + return (JitLocalSourceMap*)u_sess->mot_cxt.jit_session_source_map; +} + +inline void SetLocalSourceMap(JitLocalSourceMap* localMap) +{ + MOT_ASSERT(u_sess->mot_cxt.jit_session_source_map == nullptr); + MOT_ASSERT(localMap != nullptr); + u_sess->mot_cxt.jit_session_source_map = localMap; +} + +inline void ClearLocalSourceMap() +{ + MOT_ASSERT(u_sess->mot_cxt.jit_session_source_map != nullptr); + u_sess->mot_cxt.jit_session_source_map = nullptr; +} + +static JitLocalSourceMap* GetOrCreateLocalSourceMap() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr) { + size_t allocSize = sizeof(JitLocalSourceMap); + char* buf = (char*)MOT::MemSessionAlloc(allocSize); + if (buf == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "JIT Notify DDL", + "Failed to allocate %u bytes for session-local JIT source map", + allocSize); + return nullptr; + } + localMap = new (buf) JitLocalSourceMap(); + SetLocalSourceMap(localMap); + MOT_LOG_DEBUG("JIT-TXN: Created session-local JIT source map"); + } + return localMap; +} + +static void DestroyLocalSourceMap() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + MOT_LOG_DEBUG("JIT-TXN: Clearing local jit-source map"); + ClearJitSourceMapImpl(&localMap->m_namespaceMap, true, false); + localMap->~JitLocalSourceMap(); + MOT::MemSessionFree(localMap); + ClearLocalSourceMap(); + MOT_LOG_DEBUG("JIT-TXN: Destroyed session-local JIT source map"); + } +} extern bool InitJitSourceMap() { g_jitSourceMap.m_initialized = 0; - int res = pthread_mutex_init(&g_jitSourceMap.m_lock, NULL); + // we need a recursive mutex, due to complexities when scheduling ready-for-recycle deprecate source for cleanup + // (see RemoveJitSourceContext() calling ScheduleDeprecateJitSourceCleanUp(), which might be triggered when calling + // HandleLocalJitSourceChanges()) + pthread_mutexattr_t attr; + int res = pthread_mutexattr_init(&attr); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE(res, + pthread_mutexattr_init, + "Code Generation", + "Failed to init mutex attributes for global JIT source map"); + return false; + } + + (void)pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + // create map lock +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK + res = pthread_rwlock_init(&g_jitSourceMap.m_rwlock, NULL); if (res != 0) { MOT_REPORT_SYSTEM_ERROR_CODE( - res, pthread_mutex_init, "Code Generation", "Failed to create mutex for global jit-source map"); - } else { - g_jitSourceMap.m_initialized = 1; + res, pthread_rwlock_init, "Code Generation", "Failed to create lock for global JIT source map"); + return false; } - return g_jitSourceMap.m_initialized != 0 ? true : false; +#else + res = pthread_mutex_init(&g_jitSourceMap.m_lock, NULL); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE( + res, pthread_mutex_init, "Code Generation", "Failed to create lock for global JIT source map"); + return false; + } +#endif + + // create deprecate sources lock + res = pthread_mutex_init(&g_jitSourceMap.m_deprecateLock, &attr); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE( + res, pthread_mutex_init, "Code Generation", "Failed to create lock for deprecate JIT sources"); +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK + (void)pthread_rwlock_destroy(&g_jitSourceMap.m_rwlock); +#else + (void)pthread_mutex_destroy(&g_jitSourceMap.m_lock); +#endif + return false; + } + + g_jitSourceMap.m_initialized = 1; + return true; } extern void DestroyJitSourceMap() { if (g_jitSourceMap.m_initialized) { ClearJitSourceMap(); - pthread_mutex_destroy(&g_jitSourceMap.m_lock); + (void)pthread_mutex_destroy(&g_jitSourceMap.m_deprecateLock); +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK + (void)pthread_rwlock_destroy(&g_jitSourceMap.m_rwlock); +#else + (void)pthread_mutex_destroy(&g_jitSourceMap.m_lock); +#endif g_jitSourceMap.m_initialized = 0; } } +static void ClearJitSourceMapImpl(JitNamespaceMapType* namespaceMap, bool issueWarnings, bool isShutdown) +{ + // since there are inter-dependencies between sources, we clear the map in repeated loops, such that independent + // sources are first released + MOT::LogLevel logLevel = issueWarnings ? MOT::LogLevel::LL_WARN : MOT::LogLevel::LL_TRACE; + const char* mapType = (namespaceMap == &g_jitSourceMap.m_namespaceMap) ? "global" : "local"; + MOT_LOG_TRACE("Destroying %s source map", mapType); + bool done = false; + uint32_t round = 0; + while (!done) { + uint32_t sourcesDestroyed = 0; + MOT_LOG_TRACE("Destroying %s source map with %u name spaces", mapType, (uint32_t)namespaceMap->size()); + JitNamespaceMapType::iterator itr = namespaceMap->begin(); + while (itr != namespaceMap->end()) { + JitSourceMapType& sourceMap = itr->second; + MOT_LOG_TRACE("Clearing %u JIT source objects under name space: [%s]", + (uint32_t)sourceMap.size(), + itr->first.c_str()); + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + MOT_LOG( + logLevel, "Found JIT source %p while cleaning source map: %s", jitSource, jitSource->m_queryString); + if (!CleanUpDeprecateJitSourceContexts(jitSource)) { + MOT_LOG(logLevel, "Skipping destroy of busy source %p: %s", jitSource, jitSource->m_queryString); + ++sourceItr; + } else { + sourceItr = sourceMap.erase(sourceItr); + DestroyJitSource(jitSource); + ++sourcesDestroyed; + } + } + if (sourceMap.empty()) { + MOT_LOG_TRACE("Source map under name-space [%s] cleared", itr->first.c_str()); + itr = namespaceMap->erase(itr); + } else { + MOT_LOG(logLevel, + "Source map under name-space [%s] not cleared completely (%u sources left)", + itr->first.c_str(), + (uint32_t)sourceMap.size()); + ++itr; + } + } + MOT_LOG_TRACE("Destroyed %u sources in round %u", sourcesDestroyed, ++round); + if (namespaceMap->size() == 0) { + done = true; + } else if (!isShutdown) { + done = true; + } else if (sourcesDestroyed == 0) { + MOT_LOG(logLevel, "Cannot clear %s source map (circular dependency?)", mapType); + done = true; + } + if (!done) { + if (isShutdown) { + CleanUpDeprecateJitSources(); + } else { + CleanUpReadyDeprecateJitSources(); + } + } + } + + if (!namespaceMap->empty()) { + MOT_LOG(logLevel, "Could not clear %s source map", mapType); + } +} + extern void ClearJitSourceMap() { MOT_LOG_TRACE("Clearing global jit-source map"); - JitSourceMapType::iterator itr = g_jitSourceMap.m_sourceMap.begin(); - while (itr != g_jitSourceMap.m_sourceMap.end()) { - JitSource* jitSource = itr->second; - FreePooledJitSource(jitSource); - itr = g_jitSourceMap.m_sourceMap.erase(itr); - } + LockJitSourceMap(); + PruneJitSourceMap(); + CleanUpDeprecateJitSources(); + ClearJitSourceMapImpl(&g_jitSourceMap.m_namespaceMap, false, true); + UnlockJitSourceMap(); } -extern void LockJitSourceMap() +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK +extern void LockJitSourceMap(bool readLock /* = false */) { - pthread_mutex_lock(&g_jitSourceMap.m_lock); + if (readLock) { + (void)pthread_rwlock_rdlock(&g_jitSourceMap.m_rwlock); + } else { + (void)pthread_rwlock_wrlock(&g_jitSourceMap.m_rwlock); + } } extern void UnlockJitSourceMap() { - pthread_mutex_unlock(&g_jitSourceMap.m_lock); + (void)pthread_rwlock_unlock(&g_jitSourceMap.m_rwlock); +} +#else +extern void LockJitSourceMap() +{ + (void)pthread_mutex_lock(&g_jitSourceMap.m_lock); +} + +extern void UnlockJitSourceMap() +{ + (void)pthread_mutex_unlock(&g_jitSourceMap.m_lock); +} +#endif + +inline void LockDeprecateJitSources() +{ + (void)pthread_mutex_lock(&g_jitSourceMap.m_deprecateLock); +} + +inline void UnlockDeprecateJitSources() +{ + (void)pthread_mutex_unlock(&g_jitSourceMap.m_deprecateLock); +} + +static uint32_t GetJitSourceMapSize(JitNamespaceMapType* namespaceMap) +{ + uint32_t size = 0; + JitNamespaceMapType::iterator itr = namespaceMap->begin(); + while (itr != namespaceMap->end()) { + size += itr->second.size(); + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator mapItr = sourceMap.begin(); + // count also non-jittable sub-queries + while (mapItr != sourceMap.end()) { + JitSource* jitSource = mapItr->second; + size += GetJitSourceNonJitQueryCount(jitSource); + ++mapItr; + } + ++itr; + } + return size; } extern uint32_t GetJitSourceMapSize() { - return g_jitSourceMap.m_sourceMap.size(); + // this is a maximum bound on map size + uint32_t size = GetJitSourceMapSize(&g_jitSourceMap.m_namespaceMap); + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + size += GetJitSourceMapSize(&localMap->m_namespaceMap); + } + return size; } -extern JitSource* GetCachedJitSource(const char* queryString) +extern bool PushJitSourceNamespace(Oid functionId, const char* queryNamespace) { - JitSource* result = NULL; - JitSourceMapType::iterator itr = g_jitSourceMap.m_sourceMap.find(queryString); - if (itr != g_jitSourceMap.m_sourceMap.end()) { - result = itr->second; + size_t allocSize = sizeof(JitNamespace); + JitNamespace* nameSpace = (JitNamespace*)MOT::MemSessionAlloc(allocSize); + if (nameSpace == nullptr) { + MOT_REPORT_ERROR( + MOT_ERROR_OOM, "JIT Push Namespace", "Failed to allocate %u bytes for JIT Namespace", allocSize); + return false; + } + + errno_t erc = strcpy_s(nameSpace->m_namespace, sizeof(nameSpace->m_namespace), queryNamespace); + securec_check(erc, "\0", "\0"); + nameSpace->m_functionId = functionId; + nameSpace->m_next = (JitNamespace*)u_sess->mot_cxt.jit_ns_stack; + u_sess->mot_cxt.jit_ns_stack = nameSpace; + MOT_LOG_TRACE("Pushed JIT SP Namespace: %s", nameSpace->m_namespace); + return true; +} + +extern void PopJitSourceNamespace() +{ + JitNamespace* nameSpace = (JitNamespace*)u_sess->mot_cxt.jit_ns_stack; + if (nameSpace) { + u_sess->mot_cxt.jit_ns_stack = nameSpace->m_next; + MOT_LOG_TRACE("Popped JIT SP Namespace: %s", nameSpace->m_namespace); + MOT::MemSessionFree(nameSpace); + } +} + +extern const char* GetActiveNamespace() +{ + JitNamespace* nameSpace = (JitNamespace*)u_sess->mot_cxt.jit_ns_stack; + if (nameSpace == nullptr) { + return MOT_JIT_GLOBAL_QUERY_NS; + } else { + return nameSpace->m_namespace; + } +} + +extern Oid GetActiveNamespaceFunctionId() +{ + JitNamespace* nameSpace = (JitNamespace*)u_sess->mot_cxt.jit_ns_stack; + if (nameSpace == nullptr) { + return InvalidOid; + } else { + return nameSpace->m_functionId; + } +} + +inline bool IsActiveNamespaceGlobal() +{ + return (u_sess->mot_cxt.jit_ns_stack == nullptr); +} + +extern JitSource* GetCachedJitSource(const char* queryString, bool globalNamespace /* = false */) +{ + JitSource* jitSource = nullptr; + const char* queryNamespace = globalNamespace ? MOT_JIT_GLOBAL_QUERY_NS : GetActiveNamespace(); + MOT_LOG_TRACE("Searching in JIT source map name space %s for query: %s", queryNamespace, queryString); + + // if local map exists, then we search only in local map + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + MOT_LOG_TRACE("Executing search from local map"); + jitSource = FindCachedJitSource(queryString, queryNamespace, &localMap->m_namespaceMap); + } else { + // otherwise we search in global map + MOT_LOG_TRACE("Executing search from global map"); + jitSource = FindCachedJitSource(queryString, queryNamespace, &g_jitSourceMap.m_namespaceMap); + } + + const char* mapType = (localMap != nullptr) ? "local" : "global"; + if (jitSource != nullptr) { + MOT_LOG_TRACE("JIT source %p with state %s found in %s source map", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + mapType); + } else { + MOT_LOG_TRACE("JIT source not found in %s source map, aborting search", mapType); + } + return jitSource; +} + +extern bool AddCachedJitSource(JitSource* cachedJitSource, bool globalNamespace /* = false */) +{ + // We add new JIT source ALWAYS to local map (everything is transactional up to commit/rollback) + const char* queryNamespace = globalNamespace ? MOT_JIT_GLOBAL_QUERY_NS : GetActiveNamespace(); + + // if there was at least one DDL then we add the query to the local map + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + MOT_LOG_TRACE("Inserting JIT source %p to local source map under query name-space [%s] with query string: %s", + cachedJitSource, + queryNamespace, + cachedJitSource->m_queryString); + + MOT_ASSERT(cachedJitSource->m_usage == JIT_CONTEXT_LOCAL); + return AddCachedJitSource(&localMap->m_namespaceMap, queryNamespace, cachedJitSource); + } + + MOT_LOG_TRACE("Inserting JIT source %p to global source map under query name-space [%s] with query string: %s", + cachedJitSource, + queryNamespace, + cachedJitSource->m_queryString); + MOT_ASSERT(cachedJitSource->m_usage == JIT_CONTEXT_GLOBAL); + return AddCachedJitSource(&g_jitSourceMap.m_namespaceMap, queryNamespace, cachedJitSource); +} + +extern bool ContainsReadyCachedJitSource( + const char* queryString, bool globalNamespace, bool allowUnavailable /* = false */) +{ + const char* queryNamespace = globalNamespace ? MOT_JIT_GLOBAL_QUERY_NS : GetActiveNamespace(); + MOT_LOG_TRACE("Searching in JIT source map name space %s for query: %s", queryNamespace, queryString); + + // search in local map first if exists + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + MOT_LOG_TRACE("Executing search from local map"); + JitSource* jitSource = FindCachedJitSource(queryString, queryNamespace, &localMap->m_namespaceMap); + // no need to lock local JIT source since it is accessed only by current session + if ((jitSource != nullptr) && (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) && + (jitSource->m_sourceJitContext != nullptr)) { + MOT_LOG_TRACE("Ready JIT source %p found in session-local source map", jitSource); + return true; + } + if (allowUnavailable && (jitSource != nullptr) && + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE)) { + MOT_LOG_TRACE("Unavailable JIT source %p found in session-local source map, considered ready", jitSource); + return true; + } + MOT_LOG_TRACE("JIT source %p found in session-local source map, but is not ready: %s", jitSource, queryString); + return false; // if local map exists we make decision only from local map + } + + // otherwise we search in global map + MOT_LOG_TRACE("Executing search from global map"); + bool result = false; + JitCodegenState codegenState = JitCodegenState::JIT_CODEGEN_NONE; + LockJitSourceMap(); + JitSource* jitSource = FindCachedJitSource(queryString, queryNamespace, &g_jitSourceMap.m_namespaceMap); + if (jitSource != nullptr) { + LockJitSource(jitSource); + codegenState = jitSource->m_codegenState; + if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) && + (jitSource->m_sourceJitContext != nullptr)) { + result = true; + } else if (allowUnavailable && (jitSource != nullptr) && + (jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE)) { + MOT_LOG_TRACE("Unavailable JIT source %p found in global source map, considered ready", jitSource); + result = true; + } + UnlockJitSource(jitSource); + } + UnlockJitSourceMap(); + if (result) { + MOT_LOG_TRACE("Ready JIT source %p found in global source map", jitSource); + } else { + MOT_LOG_TRACE("Ready JIT source NOT found (%p:%s) in global source map", + jitSource, + JitCodegenStateToString(codegenState)); } return result; } -extern bool AddCachedJitSource(JitSource* cachedJitSource) +extern void RemoveCachedGlobalJitSource(JitSource* jitSource, const char* nameSpace) { - MOT_LOG_TRACE("Inserting JIT source %p to global source map on query string: %s", - cachedJitSource, - cachedJitSource->_query_string); - char* queryString = cachedJitSource->_query_string; // required due to compiler error - return g_jitSourceMap.m_sourceMap.insert(JitSourceMapType::value_type(queryString, cachedJitSource)).second; + bool found = false; + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.find(nameSpace); + if (itr != g_jitSourceMap.m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.find(jitSource->m_queryString); + if (sourceItr != sourceMap.end()) { + if (sourceItr->second != jitSource) { + MOT_LOG_TRACE("Mismatching pointers, required %p, found %p: %s", + jitSource, + sourceItr->second, + jitSource->m_queryString); + } else { + (void)sourceMap.erase(sourceItr); + MOT_LOG_TRACE("JIT source %p removed from global map under name-space %s: %s", + jitSource, + nameSpace, + jitSource->m_queryString); + found = true; + } + } + } else { + MOT_LOG_TRACE("Name-space %s not found in global map", nameSpace); + } + + if (!found) { + MOT_LOG_TRACE("Failed to remove JIT source %p from global map (name-space %s not found): %s", + jitSource, + nameSpace, + jitSource->m_queryString); + } } -extern bool ContainsReadyCachedJitSource(const char* queryString) +extern JitSource* GetCachedGlobalJitSource(const char* queryString, const char* nameSpace) +{ + MOT_LOG_TRACE("Searching in global source map under name-space %s for query: %s", nameSpace, queryString); + JitSource* jitSource = FindCachedJitSource(queryString, nameSpace, &g_jitSourceMap.m_namespaceMap); + if (jitSource != nullptr) { + MOT_LOG_TRACE("JIT source %p with state %s found in global source map", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState)); + } else { + MOT_LOG_TRACE("JIT source not found in global source map, aborting search"); + } + return jitSource; +} + +static void TraceJitDetail(MotJitDetail* entry) +{ + MOT_LOG_DEBUG("JIT entry: procOid=%u, query=%s, namespace=%s, jittableStatus=%s, validStatus=%s, planType=%s", + (unsigned)entry->procOid, + entry->query, + entry->nameSpace, + entry->jittableStatus, + entry->validStatus, + entry->planType); +} + +static void GetMotJitDetail(JitSource* jitSource, MotJitDetail* entry) +{ + // get common fields + entry->procOid = InvalidOid; + entry->jittableStatus = pstrdup(JitSourceStatusToString(jitSource->m_status)); + entry->lastUpdatedTimestamp = jitSource->m_timestamp; + + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + entry->procOid = jitSource->m_functionOid; + char* dotPtr = strchr(jitSource->m_queryString, '.'); + if (dotPtr == nullptr) { + entry->query = pstrdup(jitSource->m_queryString); + } else { + // we shave off the function id + size_t dotPos = dotPtr - jitSource->m_queryString; + entry->query = pnstrdup(jitSource->m_queryString, dotPos); + } + } else { + entry->query = pstrdup(jitSource->m_queryString); + } + + if (jitSource->m_sourceJitContext == nullptr) { + entry->validStatus = pstrdup(JitCodegenStateToString(jitSource->m_codegenState)); + entry->planType = pstrdup(""); + } else { + uint8_t validState = MOT_ATOMIC_LOAD(jitSource->m_sourceJitContext->m_validState); + entry->validStatus = pstrdup(JitContextValidStateToString(validState)); + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + JitFunctionContext* functionContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + entry->procOid = functionContext->m_functionOid; + entry->planType = pstrdup("SP"); + } else { + entry->planType = pstrdup(CommandToString(jitSource->m_commandType)); + } + } + + entry->codegenTime = (int64)jitSource->m_codegenStats.m_codegenTime; + entry->verifyTime = (int64)jitSource->m_codegenStats.m_verifyTime; + entry->finalizeTime = (int64)jitSource->m_codegenStats.m_finalizeTime; + entry->compileTime = (int64)jitSource->m_codegenStats.m_compileTime; + TraceJitDetail(entry); +} + +static uint32_t GetMotJitDetailNonJitSubQuery(JitSource* jitSource, MotJitDetail* entry) +{ + if (jitSource->m_sourceJitContext == nullptr) { + return 0; + } + if (jitSource->m_sourceJitContext->m_contextType != JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + return 0; + } + uint32_t count = 0; + JitFunctionContext* jitContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + for (uint32_t i = 0; i < jitContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &jitContext->m_SPSubQueryList[i]; + if (callSite->m_queryContext == nullptr) { + entry->nameSpace = pstrdup(jitSource->m_queryString); + entry->procOid = InvalidOid; + entry->jittableStatus = pstrdup(JitSourceStatusToString(JitSourceStatus::JIT_SOURCE_UNJITTABLE)); + entry->lastUpdatedTimestamp = jitSource->m_timestamp; + entry->query = pstrdup(callSite->m_queryString); + entry->validStatus = pstrdup(JitContextValidStateToString(JIT_CONTEXT_VALID)); + entry->planType = pstrdup("FDW"); + entry->codegenTime = 0; + entry->verifyTime = 0; + entry->finalizeTime = 0; + entry->compileTime = 0; + TraceJitDetail(entry); + ++count; + ++entry; + } + } + return count; +} + +#ifdef MOT_JIT_TEST +constexpr uint32_t MOT_JIT_TEST_QUERY_LEN = 64; +extern uint64_t totalQueryExecCount; +extern uint64_t totalFunctionExecCount; +static void GetMotJitTestDetail(MotJitDetail* entry, bool query) +{ + entry->nameSpace = pstrdup("TEST"); + entry->procOid = 9999; + entry->lastUpdatedTimestamp = 0; + char buf[MOT_JIT_TEST_QUERY_LEN]; + errno_t rc; + if (query) { + entry->query = pstrdup("query_count"); + uint64_t queryCount = MOT_ATOMIC_LOAD(totalQueryExecCount); + rc = snprintf_s(buf, MOT_JIT_TEST_QUERY_LEN, MOT_JIT_TEST_QUERY_LEN - 1, "%" PRIu64, queryCount); + securec_check_ss(rc, "\0", "\0"); + } else { + entry->query = pstrdup("function_count"); + uint64_t functionCount = MOT_ATOMIC_LOAD(totalFunctionExecCount); + rc = snprintf_s(buf, MOT_JIT_TEST_QUERY_LEN, MOT_JIT_TEST_QUERY_LEN - 1, "%" PRIu64, functionCount); + securec_check_ss(rc, "\0", "\0"); + } + entry->jittableStatus = pstrdup(buf); + entry->validStatus = pstrdup(""); + entry->planType = pstrdup(""); + entry->codegenTime = 0; + entry->verifyTime = 0; + entry->finalizeTime = 0; + entry->compileTime = 0; + TraceJitDetail(entry); +} +static void GetMotJitTestDetailSafe(MotJitDetail* entry, bool query) +{ + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + GetMotJitTestDetail(entry, query); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while getting JIT source detail test counters: %s", edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); +} +#endif + +static void GetMotJitDetailSafe(JitSource* jitSource, MotJitDetail* entry) +{ + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + GetMotJitDetail(jitSource, entry); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN( + "Caught exception while getting JIT source '%s' detail: %s", jitSource->m_queryString, edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); +} + +static uint32_t GetMotJitDetailNonJitSubQuerySafe(JitSource* jitSource, MotJitDetail* entry) +{ + volatile uint32_t result = 0; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + result = GetMotJitDetailNonJitSubQuery(jitSource, entry); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while getting JIT source '%s' non-jittable sub-query detail: %s", + jitSource->m_queryString, + edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + return (uint32_t)result; +} + +extern MotJitDetail* GetJitSourceDetailImpl(uint32_t* num) +{ + uint32_t maxSize = GetJitSourceMapSize(); +#ifdef MOT_JIT_TEST + maxSize += 2; +#endif + MotJitDetail* result = (MotJitDetail*)palloc(maxSize * sizeof(MotJitDetail)); + if (result == nullptr) { + return nullptr; + } + size_t allocSize = maxSize * sizeof(MotJitDetail); + errno_t erc = memset_s(result, allocSize, 0, allocSize); + securec_check(erc, "\0", "\0"); + + // report first entries from local map if any + MOT_LOG_DEBUG("Reporting local source map JIT detail"); + uint32_t count = 0; + std::set querySet; + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + JitNamespaceMapType::iterator itr = localMap->m_namespaceMap.begin(); + while (itr != localMap->m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + (void)querySet.insert(jitSource->m_queryString); + result[count].nameSpace = pstrdup(itr->first.c_str()); + LockJitSource(jitSource); + GetMotJitDetailSafe(jitSource, &result[count]); + ++count; + count += GetMotJitDetailNonJitSubQuerySafe(jitSource, &result[count]); + UnlockJitSource(jitSource); + ++sourceItr; + } + ++itr; + } + } + + // function are registered under the global name-space + MOT_LOG_DEBUG("Reporting global source map JIT detail"); + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.begin(); + while (itr != g_jitSourceMap.m_namespaceMap.end()) { + bool isGlobalNamespace = (strcmp(itr->first.c_str(), MOT_JIT_GLOBAL_QUERY_NS) == 0); + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + LockJitSource(jitSource); + if (!isGlobalNamespace || (querySet.find(jitSource->m_queryString) == querySet.end())) { + result[count].nameSpace = pstrdup(itr->first.c_str()); + GetMotJitDetailSafe(jitSource, &result[count]); + ++count; + count += GetMotJitDetailNonJitSubQuerySafe(jitSource, &result[count]); + } + UnlockJitSource(jitSource); + ++sourceItr; + } + ++itr; + } + +#ifdef MOT_JIT_TEST + GetMotJitTestDetailSafe(&result[count], true); + ++count; + GetMotJitTestDetailSafe(&result[count], false); + ++count; +#endif + *num = count; + return result; +} + +extern MotJitDetail* GetJitSourceDetail(uint32_t* num) +{ + *num = 0; + +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK + LockJitSourceMap(true); +#else + LockJitSourceMap(); +#endif + + volatile MotJitDetail* result = nullptr; + volatile MemoryContext origCxt = CurrentMemoryContext; + PG_TRY(); + { + result = GetJitSourceDetailImpl(num); + } + PG_CATCH(); + { + (void)MemoryContextSwitchTo(origCxt); + ErrorData* edata = CopyErrorData(); + MOT_LOG_WARN("Caught exception while getting JIT source detail: %s", edata->message); + FlushErrorState(); + FreeErrorData(edata); + } + PG_END_TRY(); + + UnlockJitSourceMap(); + return (MotJitDetail*)result; +} + +static bool PurgeJitSourceMapQuery( + JitNamespaceMapType* namespaceMap, uint64_t relationId, bool purgeOnly, bool cloneToLocalMap) +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if ((localMap == nullptr) && cloneToLocalMap) { + MOT_LOG_TRACE("Failed to get session-local source map while purging by relation"); + return false; + } + + JitNamespaceMapType::iterator itr = namespaceMap->begin(); + while (itr != namespaceMap->end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + MOT_LOG_TRACE("Purging all simple-query JIT source objects under name-space [%s]", itr->first.c_str()); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + MOT_LOG_DEBUG("Checking JIT source %p with source context %p, query: %s", + jitSource, + jitSource->m_sourceJitContext, + jitSource->m_queryString); + if (IsSimpleQuerySource(jitSource) && JitSourceRefersRelation(jitSource, relationId, false)) { + // we clone to local session map only simple queries (including SP queries) + if (cloneToLocalMap) { + MOT_LOG_TRACE("Candidate clone JIT source %p with source context %p to local map: %s", + jitSource, + jitSource->m_sourceJitContext, + jitSource->m_queryString); + // we clone only if it was not cloned before already + JitSource* localSource = + FindCachedJitSource(jitSource->m_queryString, itr->first.c_str(), &localMap->m_namespaceMap); + if (localSource == nullptr) { + MOT_LOG_TRACE("Cloning JIT source %p to local map: %s", jitSource, jitSource->m_queryString); + JitSourceOp sourceOp = + purgeOnly ? JitSourceOp::JIT_SOURCE_PURGE_ONLY : JitSourceOp::JIT_SOURCE_INVALIDATE; + localSource = CloneLocalJitSource(jitSource, purgeOnly, sourceOp); + if (localSource == nullptr) { + MOT_LOG_TRACE("Failed to clone simple-query JIT source"); + return false; + } + if (!AddCachedJitSource(&localMap->m_namespaceMap, itr->first.c_str(), localSource)) { + MOT_LOG_TRACE("Failed to add simple-query JIT source to local map"); + LockJitSource(jitSource); + MoveCurrentSessionContexts(localSource, jitSource, JitSourceOp::JIT_SOURCE_NO_OP); + UnlockJitSource(jitSource); + DestroyJitSource(localSource); + return false; + } + } + + // make sure source in local map is purged and expired + jitSource = localSource; + } + + // purge must take place in any case + MOT_LOG_TRACE("Purging cached JIT source %p by relation id %" PRIu64 + " under name-space [%s] with query: %s", + jitSource, + relationId, + itr->first.c_str(), + jitSource->m_queryString); + PurgeJitSource(jitSource, relationId); + if (!purgeOnly) { + // 1. in case of a simple query, then we want to set query as expired and invalidate all containing + // JIT context objects in all sessions (we are guarded by PG global DDL lock) + // 2. in case of a SP we just want to mark the SP as invalid (i.e. some leaf requires code- + // regeneration). This happens implicitly when we follow case 1. + // 3. in case of INVOKE context we just want the relevant SP leaf nodes to be marked as invalid. + // This is achieved already by case 1. + if ((jitSource->m_sourceJitContext != nullptr) && + (jitSource->m_sourceJitContext->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY)) { + MOT_LOG_TRACE("Setting JIT source %p as expired: %s", jitSource, jitSource->m_queryString); + JitQueryContext* queryContext = (JitQueryContext*)jitSource->m_sourceJitContext; + if (queryContext->m_commandType != JIT_COMMAND_INVOKE) { + // (containing JIT source deleted during db shutdown) + SetJitSourceExpired(jitSource, relationId); + } + } + } + } + ++sourceItr; + } + ++itr; + } + return true; +} + +static void PurgeJitSourceMapQuery(uint64_t relationId, bool purgeOnly) +{ + // in case some JIT source is invalid, we cannot tell whether it should be moved to local map (because the table + // is resides in the deleted context. As a result it might get revalidated on the global map and fail to follow + // transactional semantics (since it can get revalidated based on new definitions). For this reason we create a + // local map unconditionally, such that when revalidation takes place, we can tell that at least one DDL took place + // in the current transaction, and so we need to clone first the jit source to the local map before revalidation + // takes place. + MOT_LOG_TRACE("Purging JIT source map queries by relation %" PRIu64 " (purgeOnly: %s)", + relationId, + purgeOnly ? "true" : "false"); + JitLocalSourceMap* localMap = GetOrCreateLocalSourceMap(); + if (localMap == nullptr) { + MOT_LOG_ERROR("PurgeJitSourceMapQuery: Failed to create session-local source map"); + return; + } + + MOT_LOG_TRACE("Purging local source map"); + (void)PurgeJitSourceMapQuery(&localMap->m_namespaceMap, relationId, purgeOnly, false); + + // now purge global map, such that each purged source is cloned and moved to the local map before purge/expire + LockJitSourceMap(); + MOT_LOG_TRACE("Purging global source map"); + if (!PurgeJitSourceMapQuery(&g_jitSourceMap.m_namespaceMap, relationId, purgeOnly, true)) { + MOT_LOG_ERROR("Failed to purge global source map by relation %" PRIu64 " (purgeOnly: %s)", + relationId, + purgeOnly ? "true" : "false"); + } + UnlockJitSourceMap(); + + // we purge deprecate source now, even if roll-back will be issued later, since this is a safe point to purge the + // deprecate source and all its associated context objects (because we hold exclusive lock on relation) + PurgeDeprecateQueryList(relationId); + + // recycle deprecate sources that are no longer referenced + CleanUpReadyDeprecateJitSources(); +} + +inline bool IsInvokeSPQuerySource(JitSource* jitSource, Oid functionId) +{ + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) && + (jitSource->m_commandType == JIT_COMMAND_INVOKE)) { + if (jitSource->m_functionOid == functionId) { + return true; + } else { + MOT_LOG_TRACE("Invoke query refers function %u", jitSource->m_functionOid); + } + } + return false; +} + +inline bool IsSPSource(JitSource* jitSource, Oid functionId) +{ + return ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (jitSource->m_functionOid == functionId)); +} + +static bool PurgeJitSourceMapSP( + JitNamespaceMapType* namespaceMap, Oid functionId, bool replaceFunction, bool cloneToLocalMap) +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if ((localMap == nullptr) && cloneToLocalMap) { + MOT_LOG_TRACE("Failed to get session-local source map while purging by function"); + return false; + } + + JitNamespaceMapType::iterator itr = namespaceMap->begin(); + while (itr != namespaceMap->end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + MOT_LOG_TRACE("Purging all invoke-query/sp JIT source objects under name-space [%s]", itr->first.c_str()); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + MOT_LOG_DEBUG("Checking JIT source %p with source context %p, query: %s", + jitSource, + jitSource->m_sourceJitContext, + jitSource->m_queryString); + // we treat here only invoke queries and SPs + // we clone to local session map only top-level INVOKE queries and directly invoked SP (this can include + // also sub-SP) + if (cloneToLocalMap && + (IsInvokeSPQuerySource(jitSource, functionId) || IsSPSource(jitSource, functionId))) { + MOT_LOG_TRACE("Candidate clone JIT source %p with source context %p to local map: %s", + jitSource, + jitSource->m_sourceJitContext, + jitSource->m_queryString); + // we clone only if it was not cloned before already + JitSource* localSource = + FindCachedJitSource(jitSource->m_queryString, itr->first.c_str(), &localMap->m_namespaceMap); + if (localSource == nullptr) { + MOT_LOG_TRACE("Cloning JIT source %p to local map: %s", jitSource, jitSource->m_queryString); + // trigger full invalidation for all session-local related contexts + JitSourceOp sourceOp = replaceFunction ? JitSourceOp::JIT_SOURCE_REPLACE_FUNCTION + : JitSourceOp::JIT_SOURCE_DROP_FUNCTION; + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY) { + sourceOp = JitSourceOp::JIT_SOURCE_INVALIDATE; + } + localSource = CloneLocalJitSource(jitSource, false, sourceOp); + if (localSource == nullptr) { + MOT_LOG_TRACE("Failed to clone invoke-query/sp JIT source"); + return false; + } + if (!AddCachedJitSource(&localMap->m_namespaceMap, itr->first.c_str(), localSource)) { + MOT_LOG_TRACE("Failed to add invoke-query/sp JIT source to local map"); + LockJitSource(jitSource); + MoveCurrentSessionContexts(localSource, jitSource, JitSourceOp::JIT_SOURCE_NO_OP); + UnlockJitSource(jitSource); + DestroyJitSource(localSource); + return false; + } + } + + // make sure source in local map is purged and expired + jitSource = localSource; + } + // when a DDL affects a global source we are done, since an empty local copy was made, and it will be + // revalidated upon first attempt to access it + // when a DDL affects a local map copy of SP we need to expire the source and all its queries + if (cloneToLocalMap && IsSPSource(jitSource, functionId)) { + SetJitSourceExpired(jitSource, 0, replaceFunction); + } + ++sourceItr; + } + ++itr; + } + return true; +} + +static bool HasLocalJitFunctionSource(Oid functionOId, const char* nameSpace) +{ + MOT_LOG_TRACE( + "Searching for function JIT source under %s name-space in local map for function %u", nameSpace, functionOId); + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr) { + MOT_LOG_TRACE("No local map found"); + return false; + } + + JitNamespaceMapType::iterator itr = localMap->m_namespaceMap.find(nameSpace); + if (itr == localMap->m_namespaceMap.end()) { + MOT_LOG_TRACE("Name-space %s not found in local map", nameSpace); + return false; + } + + bool found = false; + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + if (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) { + if (jitSource->m_functionOid == functionOId) { + found = true; + break; + } + } + ++sourceItr; + } + + if (!found) { + MOT_LOG_TRACE( + "No function JIT source found under %s name-space in local map for function %u", nameSpace, functionOId); + } + + return found; +} + +static JitSource* CreateAndAddLocalJitSPSource(Oid functionId, const char* funcName) +{ + MOT::mot_string qualifiedFunctionName; + if (!qualifiedFunctionName.format("%s.%u", funcName, functionId)) { + MOT_LOG_TRACE("Failed to format qualified function name %s.%u", funcName, functionId); + return nullptr; + } + + MOT_LOG_TRACE("Trying to install function JIT source under global name-space to local map: %s", + qualifiedFunctionName.c_str()); + + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr) { + MOT_LOG_TRACE("Failed to get session-local map"); + return nullptr; + } + + JitSource* localSource = AllocJitSource(qualifiedFunctionName.c_str(), JIT_CONTEXT_LOCAL); + if (localSource == nullptr) { + MOT_LOG_TRACE("Failed to allocated local JIT source for function: %s", qualifiedFunctionName.c_str()); + return nullptr; + } + + localSource->m_contextType = JitContextType::JIT_CONTEXT_TYPE_FUNCTION; + localSource->m_commandType = JIT_COMMAND_FUNCTION; + localSource->m_functionOid = functionId; + + MOT_LOG_TRACE( + "Created local jit-source object %p (contextType %s, commandType %s, functionOid %u) for function: %s", + localSource, + JitContextTypeToString(localSource->m_contextType), + CommandToString(localSource->m_commandType), + localSource->m_functionOid, + qualifiedFunctionName.c_str()); + + if (!AddCachedJitSource(&localMap->m_namespaceMap, MOT_JIT_GLOBAL_QUERY_NS, localSource)) { + MOT_LOG_TRACE( + "Failed to add local jit-source object %p to local map for function: %s", qualifiedFunctionName.c_str()); + DestroyJitSource(localSource); + localSource = nullptr; + return nullptr; + } + + MOT_LOG_TRACE("Installed local jit-source object %p to local map for function: %s", + localSource, + qualifiedFunctionName.c_str()); + return localSource; +} + +static void PurgeJitSourceMapSP(Oid functionId, bool replaceFunction, const char* funcName) +{ + // all SP changes are transactional so we must have a ready map + MOT_LOG_TRACE("Purging JIT source map queries by function %s.%u (replaceFunction: %s)", + funcName, + functionId, + replaceFunction ? "true" : "false"); + JitLocalSourceMap* localMap = GetOrCreateLocalSourceMap(); + if (localMap == nullptr) { + MOT_LOG_ERROR("PurgeJitSourceMapSP: Failed to create session-local source map"); + return; + } + + MOT_LOG_TRACE("Purging local map"); + (void)PurgeJitSourceMapSP(&localMap->m_namespaceMap, functionId, replaceFunction, false); + + // record operation for later pruning + JitSPOperation spOp = replaceFunction ? JitSPOperation::JIT_SP_REPLACE : JitSPOperation::JIT_SP_DROP; + localMap->m_operationMap[functionId] = spOp; + MOT_LOG_TRACE("Recorded function %u operation: %s", functionId, replaceFunction ? "REPLACE" : "DROP") + + // now purge global map, such that each purged source is cloned and moved to the local map before purge/expire + LockJitSourceMap(); + + MOT_LOG_TRACE("Purging global map"); + if (!PurgeJitSourceMapSP(&g_jitSourceMap.m_namespaceMap, functionId, replaceFunction, true)) { + MOT_LOG_ERROR("Failed to purge global source map by function %s.%u (replaceFunction: %s)", + funcName, + functionId, + replaceFunction ? "true" : "false"); + } + + // Create a JIT function source in local map, if it was not cloned into the local map. + if (!HasLocalJitFunctionSource(functionId, MOT_JIT_GLOBAL_QUERY_NS)) { + JitSource* localSource = CreateAndAddLocalJitSPSource(functionId, funcName); + if (localSource != nullptr) { + // Mark the local source as DROPPED/REPLACED. + SetJitSourceExpired(localSource, 0, replaceFunction); + } else { + MOT_LOG_ERROR("Failed to install local JIT source for function: %s.%u", funcName, functionId); + } + } + + UnlockJitSourceMap(); + + // recycle deprecate sources that are no longer referenced + CleanUpReadyDeprecateJitSources(); +} + +extern void PurgeJitSourceMap( + uint64_t objectId, JitPurgeScope purgeScope, JitPurgeAction purgeAction, const char* funcName) +{ + if (purgeScope == JIT_PURGE_SCOPE_QUERY) { + PurgeJitSourceMapQuery(objectId, purgeAction == JIT_PURGE_ONLY); + } else { + PurgeJitSourceMapSP(objectId, purgeAction == JIT_PURGE_REPLACE, funcName); + } +} + +extern bool HasJitFunctionSource(Oid functionId) { bool result = false; LockJitSourceMap(); - JitSourceMapType::iterator itr = g_jitSourceMap.m_sourceMap.find(queryString); - if (itr != g_jitSourceMap.m_sourceMap.end()) { - JitSource* jitSource = itr->second; - if ((jitSource->_status == JIT_CONTEXT_READY) && (jitSource->_source_jit_context != NULL)) { - result = true; - } - } - UnlockJitSourceMap(); - return result; -} - -extern void PurgeJitSourceMap(uint64_t relationId, bool purgeOnly) -{ - LockJitSourceMap(); - JitSourceMapType::iterator itr = g_jitSourceMap.m_sourceMap.begin(); - while (itr != g_jitSourceMap.m_sourceMap.end()) { - JitSource* jitSource = itr->second; - if (JitSourceRefersRelation(jitSource, relationId)) { - MOT_LOG_TRACE("Purging cached jit-source %p by relation id %" PRIu64 " with query: %s", - jitSource, - relationId, - jitSource->_query_string); - if (purgeOnly) { - PurgeJitSource(jitSource, relationId); - } else { - SetJitSourceExpired(jitSource, relationId); // (containing jit-source deleted during db shutdown) + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.begin(); + while (itr != g_jitSourceMap.m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* jitSource = sourceItr->second; + if ((jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (jitSource->m_sourceJitContext != nullptr)) { + JitFunctionContext* functionContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + if (functionContext->m_functionOid == functionId) { + result = true; + break; + } } + ++sourceItr; + } + if (result) { + break; } ++itr; } UnlockJitSourceMap(); + return result; +} + +inline JitSource* CloneAndAddGlobalSource(JitSource* localSource, const char* nameSpace) +{ + JitSource* globalSource = CloneGlobalJitSource(localSource); + if (globalSource == nullptr) { + MOT_LOG_ERROR("Failed to clone global JIT source: %s", localSource->m_queryString); + } else if (!AddCachedJitSource(&g_jitSourceMap.m_namespaceMap, nameSpace, globalSource)) { + MOT_LOG_ERROR("Failed to add global JIT source: %s", localSource->m_queryString); + DestroyJitSource(globalSource); + } else { + MOT_LOG_TRACE("Added source %p to global map under name-space %s: %s", + globalSource, + nameSpace, + globalSource->m_queryString); + } + return globalSource; +} + +static void HandleLocalJitSourceChanges(bool isCommit) +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr) { + return; + } + + // for the sake of proper commit we acquire exclusive locks over all involved SPs + LockJitSourceMap(); + JitSourceList deprecateList; + JitSourceList pruneList; + JitContextList rollbackInvokeContextList; + JitNamespaceMapType::iterator itr = localMap->m_namespaceMap.begin(); + while (itr != localMap->m_namespaceMap.end()) { + bool isGlobalNameSpace = (itr->first.compare(MOT_JIT_GLOBAL_QUERY_NS) == 0); + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + const char* nameSpace = itr->first.c_str(); + JitSource* localSource = sourceItr->second; + MOT_LOG_TRACE( + "Searching mapping from local source %p to global: %s", localSource, localSource->m_queryString); + + if (isCommit && !IsSimpleQuerySource(localSource)) { + JitSPOperationMap::iterator opItr = localMap->m_operationMap.find(localSource->m_functionOid); + if (opItr != localMap->m_operationMap.end()) { + // Set the current TxnId as the m_expireTxnId (TxnId which expired the JIT source). + localSource->m_expireTxnId = GetCurrentTransactionIdIfAny(); + } + } + + JitCodegenState prevGlobalCodegenState = JitCodegenState::JIT_CODEGEN_NONE; + JitSource* prevGlobalSource = + FindCachedJitSource(localSource->m_queryString, nameSpace, &g_jitSourceMap.m_namespaceMap); + JitSource* newGlobalSource = nullptr; + if (prevGlobalSource == nullptr) { + // if this is a roll-back then we need to discard the local source + if (!isCommit) { + MOT_LOG_TRACE("Could not find global source during roll-back, discarding local source: %s", + localSource->m_queryString); + } else { + MOT_LOG_TRACE("Could not find global source during commit, performing full clone: %s", + localSource->m_queryString); + newGlobalSource = CloneAndAddGlobalSource(localSource, nameSpace); + } + } else { + // ATTENTION: we cannot merge into global source that might be in use concurrently (because the jitted + // function cannot be deleted while it still executes on some other thread). This can happen only with + // SP source or INVOKE source when function was replaced or recreated (in case of top-level query and + // SP query, locks are taken before jitted function query context is invoked, so concurrent DDL cannot + // take place). In this case we clone a new global source, and mark the old global source as + // deprecated. This special flag orders any connected context to fully revalidate from scratch. + // NOTE: We also deprecate expired SP queries (i.e. they were removed during drop/replace SP) + LockJitSource(prevGlobalSource); + prevGlobalCodegenState = prevGlobalSource->m_codegenState; + if (isCommit && !IsSimpleQuerySource(prevGlobalSource)) { + // postpone deprecate until new source is installed + MOT_LOG_TRACE("Removing cached JIT source %u (%p) with state %s (TxnId %" PRIu64 "/%" PRIu64 ") " + "during commit (%lu) of non-simple query: %s", + prevGlobalSource->m_sourceId, + prevGlobalSource, + JitCodegenStateToString(prevGlobalSource->m_codegenState), + prevGlobalSource->m_functionTxnId, + prevGlobalSource->m_expireTxnId, + GetCurrentTransactionIdIfAny(), + prevGlobalSource->m_queryString); + RemoveCachedGlobalJitSource(prevGlobalSource, nameSpace); + deprecateList.PushBackDeprecate(prevGlobalSource); + if ((prevGlobalSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION) && + (prevGlobalSource->m_codegenState == JitCodegenState::JIT_CODEGEN_UNAVAILABLE)) { + // Someone is still compiling. We avoid setting the state as deprecate, but still put it in the + // deprecated list. + // We raise the m_deprecatedPendingCompile early enough so that the session compiling will + // note this after compilation and change the codegen state to JIT_CODEGEN_DEPRECATE. + MOT_LOG_TRACE("Marking JIT source %p with status unavailable as deprecate pending compile: %s", + prevGlobalSource, + prevGlobalSource->m_queryString); + prevGlobalSource->m_deprecatedPendingCompile = 1; + } + + MOT_LOG_TRACE("Cloning non-simple local source %u (%p) with state %s (TxnId: %" PRIu64 "/%" PRIu64 + ") during commit (%lu): %s", + localSource->m_sourceId, + localSource, + JitCodegenStateToString(localSource->m_codegenState), + localSource->m_functionTxnId, + localSource->m_expireTxnId, + GetCurrentTransactionIdIfAny(), + localSource->m_queryString); + + // ATTENTION: for dropped committed functions, we still clone a global copy, but we later remove it + // and deprecate it, because this way we have a common way of handling all involved contexts + // (otherwise we need to move contexts, purge, invalidate, etc. - instead we gain it from adding a + // a new global source and removing it later - code is simpler and reusable this way) + // NOTE: newGlobalSource will be locked in MergeJitSource called from CloneGlobalJitSource, + // but this will not cause any lock ordering problem with prevGlobalSource lock taken above, since + // this is a newly created source and it will be added to the global map only after the call to + // CloneGlobalJitSource. + newGlobalSource = CloneAndAddGlobalSource(localSource, nameSpace); + } else { + // no problem merging to any source after rollback, because the compiled function does not change. + // in case of commit of simple query, the old source context is deprecated so there is no problem + // of delete LLVM function while someone is still using it + if (!isCommit) { + MOT_LOG_TRACE("Merging local source %u back to global during roll-back: %s", + localSource->m_sourceId, + localSource->m_queryString); + } else { + MOT_LOG_TRACE("Merging local source %u back to global during commit of simple query: %s", + localSource->m_sourceId, + localSource->m_queryString); + } + MergeJitSource(localSource, prevGlobalSource, isCommit, &rollbackInvokeContextList); + if (!isCommit && isGlobalNameSpace) { + // restore from original global source all cached plan pointers of revalidate-fail use case + RestoreCachedPlanContexts(prevGlobalSource); + } + } + UnlockJitSource(prevGlobalSource); + } + + // prune global function source name space, and deprecate dropped functions + if ((newGlobalSource != nullptr) && + (newGlobalSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION)) { + // attention: at this point all activity regarding this source is either cloned or merged + if (newGlobalSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) { + // we prune only ready functions that were actually replaced + JitSPOperationMap::iterator opItr = localMap->m_operationMap.find(newGlobalSource->m_functionOid); + if ((opItr != localMap->m_operationMap.end()) && + (opItr->second == JitSPOperation::JIT_SP_REPLACE)) { + MOT_LOG_TRACE("Scheduling ready and replaced function %u for immediate pruning", + newGlobalSource->m_functionOid); + pruneList.PushBack(newGlobalSource); + } else { + MOT_LOG_TRACE("Ready but not replaced function %u not scheduled for pruning", + newGlobalSource->m_functionOid); + } + } else if (newGlobalSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) { + // Newly installed dropped source should be scheduled to be deprecated as part of post commit + // cleanup, except if it is unavailable. + if ((prevGlobalSource == nullptr) || + (prevGlobalCodegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE)) { + MOT_LOG_TRACE( + "Scheduling for post commit cleanup of global dropped function source %u (%p) : %s", + newGlobalSource->m_sourceId, + newGlobalSource, + newGlobalSource->m_queryString); + MOT_ASSERT(isGlobalNameSpace); + char* queryString = newGlobalSource->m_queryString; + (void)localMap->m_droppedSPMap.insert( + JitSourceMapType::value_type(queryString, newGlobalSource)); + } + pruneList.PushBack(newGlobalSource); + } + } + + if (!isCommit) { + if (IsInvokeQuerySource(localSource)) { + // In case of rollback and if this is a invoke query, we must remove all the attached + // invoke contexts and destroy them before the local function source is deprecated. + // Otherwise, deprecation of local function source will fail. + JitQueryContext* queryContext = nullptr; + MotJitContext* currItr = localSource->m_contextList; + while (currItr != nullptr) { + queryContext = (JitQueryContext*)currItr; + if (queryContext->m_invokeContext != nullptr) { + rollbackInvokeContextList.PushBack(queryContext->m_invokeContext); + queryContext->m_invokeContext = nullptr; + } + } + + if (localSource->m_sourceJitContext != nullptr) { + queryContext = (JitQueryContext*)localSource->m_sourceJitContext; + if (queryContext->m_invokeContext != nullptr) { + rollbackInvokeContextList.PushBack(queryContext->m_invokeContext); + queryContext->m_invokeContext = nullptr; + } + } + } + + // NOTE: In case of rollback, we should unlink all the session-local contexts before deprecating and + // destroying the local JIT source. + UnlinkLocalJitSourceContexts(localSource); + } + + // NOTE: In case of rollback of a invoke query source (SP re-create and rollback use case), the local + // contexts will have an invoke context attached to the function source (which is also rolled-back). + // If we try to deprecate the function source before the invoke query source, it will fail. So we defer + // the local source deprecation as well. + deprecateList.PushBackDeprecate(localSource); + ++sourceItr; + } + sourceMap.clear(); + ++itr; + } + localMap->m_namespaceMap.clear(); + localMap->m_operationMap.clear(); + + // prune name spaces of all dropped functions + MOT_LOG_TRACE("Pruning name spaces of ready-replaced and dropped functions"); + JitSource* srcItr = pruneList.Begin(); + while (srcItr != nullptr) { + JitSource* globalSource = srcItr; + srcItr = srcItr->m_next; + globalSource->m_next = nullptr; + PruneNamespace(globalSource); + } + + // cleanup leftovers (empty name spaces left after pruning) + RemoveEmptyNamespaces(); + UnlockJitSourceMap(); + + // Destroy all the collected local invoke contexts before deprecating the local sources. + MOT_LOG_TRACE("Destroying all local invoke contexts during rollback"); + MotJitContext* contextItr = rollbackInvokeContextList.Begin(); + while (contextItr != nullptr) { + MotJitContext* jitContext = contextItr; + contextItr = contextItr->m_next; + jitContext->m_next = nullptr; + DestroyJitContext(jitContext); + } + + // publish changes only after all changes have been recorded on the global map (avoid race where other session + // contexts begin to revalidate before all changes have been recorded - i.e. make change atomic as possible) + MOT_LOG_TRACE("Deprecating sources of dropped functions and local sources"); + srcItr = deprecateList.Begin(); + while (srcItr != nullptr) { + JitSource* jitSource = srcItr; + srcItr = srcItr->m_nextDeprecate; + jitSource->m_nextDeprecate = nullptr; + MarkAndAddDeprecateJitSource(jitSource, false); + } +} + +static const char* GetJitSourceNamespace(JitSource* jitSource) +{ + const char* result = nullptr; + LockJitSourceMap(); + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.begin(); + while (itr != g_jitSourceMap.m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.find(jitSource->m_queryString); + if (sourceItr != sourceMap.end()) { + result = itr->first.c_str(); + break; + } + ++itr; + } + UnlockJitSourceMap(); + return result; +} + +extern JitCodegenState RevalidateJitSourceTxn( + JitSource* jitSource, TransactionId functionTxnId /* = InvalidTransactionId */) +{ + MOT_LOG_TRACE("Revalidating JIT source %p in transaction: %s", jitSource, jitSource->m_queryString); + + // if no DDL arrived yet in this transaction we act as in the normal case + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr) { + MOT_LOG_TRACE("No local map found, revalidation continues in non-transactional manner"); + return RevalidateJitSource(jitSource, functionTxnId); + } + + // if this is a local source generated in a transaction then we revalidate in-place + if (jitSource->m_usage == JIT_CONTEXT_LOCAL) { + MOT_LOG_TRACE("JIT source is local, revalidation continues in non-transactional manner"); + return RevalidateJitSource(jitSource, functionTxnId); + } + + // This is a global source and a local map exists, so we revalidate into the local map, as revalidation relies on + // definitions found only in current transaction. + const char* nameSpace = GetJitSourceNamespace(jitSource); + if (nameSpace == nullptr) { + MOT_LOG_TRACE("Failed to get JIT source %p name-space: %s", jitSource, jitSource->m_queryString); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + + // We clone only if it was not cloned before already. + JitSource* localSource = FindCachedJitSource(jitSource->m_queryString, nameSpace, &localMap->m_namespaceMap); + if (localSource == nullptr) { + // We first create a local clone of the JIT source add it to the local map, then we continue to revalidate from + // the local source, such that revalidation is isolated in the current transaction. + MOT_LOG_TRACE( + "Transactional revalidation: Cloning jit-source %p to local map: %s", jitSource, jitSource->m_queryString); + localSource = CloneLocalJitSource(jitSource, false, JitSourceOp::JIT_SOURCE_INVALIDATE); + if (localSource == nullptr) { + MOT_LOG_TRACE("Failed to clone JIT source"); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + if (!AddCachedJitSource(&localMap->m_namespaceMap, nameSpace, localSource)) { + MOT_LOG_TRACE("Failed to add JIT source to local map: %s", localSource->m_queryString); + LockJitSource(jitSource); + MoveCurrentSessionContexts(localSource, jitSource, JitSourceOp::JIT_SOURCE_NO_OP); + UnlockJitSource(jitSource); + DestroyJitSource(localSource); + return JitCodegenState::JIT_CODEGEN_ERROR; + } + } else { + MOT_LOG_TRACE("Transactional revalidation: Found jit-source %p in local map: %s", + localSource, + localSource->m_queryString); + LockJitSource(jitSource); + MoveCurrentSessionContexts(jitSource, localSource, JitSourceOp::JIT_SOURCE_NO_OP); + UnlockJitSource(jitSource); + } + + // Revalidate the local source. + return RevalidateJitSource(localSource, functionTxnId); +} + +extern void ApplyLocalJitSourceChanges() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if ((localMap != nullptr) && !localMap->m_namespaceMap.empty()) { + MOT_LOG_DEBUG("JIT-TXN: Applying local DDL changes to JIT"); + HandleLocalJitSourceChanges(true); + MOT_LOG_DEBUG("JIT-TXN: DONE Applying local DDL changes to JIT"); + } else { + MOT_LOG_DEBUG("JIT-TXN: Skipping commit notification in JIT source map: no changes were recorded"); + } +} + +static void RecordDroppedSP(Oid functionOid, TimestampTz timestamp) +{ + LockDeprecateJitSources(); + (void)g_jitSourceMap.m_droppedSPOidMap.insert(JitSPOidMapType::value_type(functionOid, timestamp)); + UnlockDeprecateJitSources(); +} + +static void CleanupDroppedSPSources() +{ + MOT_LOG_TRACE("Deprecating sources of dropped functions (post commit cleanup)"); + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap == nullptr || localMap->m_droppedSPMap.empty()) { + return; + } + + LockJitSourceMap(); + JitSourceMapType::iterator srcItr = localMap->m_droppedSPMap.begin(); + while (srcItr != localMap->m_droppedSPMap.end()) { + // It is possible that the installed dropped source is deprecated and freed by other sessions. So we need to + // get the dropped source again from the global map, then remove and deprecate it only if it is still the + // same one we installed. + JitSource* globalSource = + FindCachedJitSource(srcItr->first.c_str(), MOT_JIT_GLOBAL_QUERY_NS, &g_jitSourceMap.m_namespaceMap); + if (globalSource != nullptr && globalSource == srcItr->second) { + LockJitSource(globalSource); + MOT_ASSERT(globalSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + bool isDropped = (globalSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) ? true : false; + Oid functionOid = globalSource->m_functionOid; + TimestampTz timestamp = globalSource->m_timestamp; + UnlockJitSource(globalSource); + if (isDropped) { + MOT_LOG_TRACE("Removing and deprecating global dropped function source %u (%p): %s", + globalSource->m_sourceId, + globalSource, + globalSource->m_queryString); + RemoveCachedGlobalJitSource(globalSource, MOT_JIT_GLOBAL_QUERY_NS); + RecordDroppedSP(functionOid, timestamp); + MarkAndAddDeprecateJitSource(globalSource, false); + } + } + ++srcItr; + } + UnlockJitSourceMap(); + localMap->m_droppedSPMap.clear(); +} + +extern void PostCommitCleanupJitSources() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if (localMap != nullptr) { + if (!localMap->m_droppedSPMap.empty()) { + MOT_LOG_DEBUG("JIT-TXN: Cleaning up dropped functions"); + CleanupDroppedSPSources(); + MOT_LOG_DEBUG("JIT-TXN: DONE Cleaning up dropped functions"); + } + MOT_ASSERT(localMap->m_namespaceMap.empty()); + MOT_ASSERT(localMap->m_operationMap.empty()); + MOT_ASSERT(localMap->m_droppedSPMap.empty()); + DestroyLocalSourceMap(); + } else { + MOT_LOG_DEBUG("JIT-TXN: Skipping post commit cleanup notification in JIT source map: no changes were recorded"); + } +} + +extern void RevertLocalJitSourceChanges() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if ((localMap != nullptr) && !localMap->m_namespaceMap.empty()) { + MOT_LOG_DEBUG("JIT-TXN: Rolling-back local DDL changes in JIT"); + HandleLocalJitSourceChanges(false); + MOT_LOG_DEBUG("JIT-TXN: DONE Rolling-back local DDL changes in JIT"); + } else { + MOT_LOG_DEBUG("JIT-TXN: Skipping rollback notification in JIT source map: no changes were recorded"); + } + DestroyLocalSourceMap(); +} + +extern void CleanupLocalJitSourceChanges() +{ + JitLocalSourceMap* localMap = GetLocalSourceMap(); + if ((localMap != nullptr) && !localMap->m_namespaceMap.empty()) { + // we treat abrupt session disconnect as rollback + MOT_LOG_DEBUG("JIT-TXN: Cleaning-up local DDL changes in JIT"); + HandleLocalJitSourceChanges(false); + MOT_LOG_DEBUG("JIT-TXN: DONE Cleaning-up local DDL changes in JIT"); + } else { + MOT_LOG_DEBUG("JIT-TXN: Skipping cleanup notification in JIT source map: no changes were recorded"); + } + DestroyLocalSourceMap(); +} + +extern void ScheduleDeprecateJitSourceCleanUp(JitSource* jitSource) +{ + MOT_LOG_TRACE("Scheduling deprecate JIT source %p for recycling: %s", jitSource, jitSource->m_queryString); + + LockDeprecateJitSources(); + + // We should not schedule for recycling if the JIT source is not in m_deprecateSources. Otherwise we will end up + // in double free, if the JIT source is directly recycled in MarkAndAddDeprecateJitSource and someone tries to + // recycle from m_readyDeprecateSources. + JitSourceSet::iterator itr = g_jitSourceMap.m_deprecateSources.find(jitSource); + if (itr != g_jitSourceMap.m_deprecateSources.end()) { + bool inserted = g_jitSourceMap.m_readyDeprecateSources.insert(jitSource).second; + if (!inserted) { + MOT_LOG_TRACE("Cannot schedule JIT source %p for recycling, item already exists: %s", + jitSource, + jitSource->m_queryString); + } + } else { + MOT_LOG_TRACE("Cannot schedule JIT source %p for recycling, item not in deprecate sources: %s", + jitSource, + jitSource->m_queryString); + } + + UnlockDeprecateJitSources(); +} + +static bool AddCachedJitSource( + JitNamespaceMapType* namespaceMap, const char* queryNamespace, JitSource* cachedJitSource) +{ + char* queryString = cachedJitSource->m_queryString; // required due to compiler error + + // access, or insert and access + JitNamespaceMapType::iterator itr = namespaceMap->find(queryNamespace); + if (itr == namespaceMap->end()) { + MOT_LOG_TRACE("Could not find name-space [%s], creating a new one", queryNamespace); + JitSourceMapType sourceMap; + (void)sourceMap.insert(JitSourceMapType::value_type(queryString, cachedJitSource)); + return namespaceMap->insert(JitNamespaceMapType::value_type(queryNamespace, sourceMap)).second; + } else { + JitSourceMapType& sourceMap = itr->second; // g_jitSourceMap.m_namespaceMap[queryNamespace]; + std::pair pairib = + sourceMap.insert(JitSourceMapType::value_type(queryString, cachedJitSource)); + if (!pairib.second) { + MOT_LOG_TRACE("Cannot add JIT source %p under name-space %s: entry %p already exists", + cachedJitSource, + queryNamespace, + pairib.first->second); + } + return pairib.second; + } +} + +static JitSource* FindCachedJitSource( + const char* queryString, const char* queryNamespace, JitNamespaceMapType* namespaceMap) +{ + JitSource* result = nullptr; + JitNamespaceMapType::iterator itr = namespaceMap->find(queryNamespace); + if (itr != namespaceMap->end()) { + JitSourceMapType& sourceMap = itr->second; + JitSourceMapType::iterator sourceItr = sourceMap.find(queryString); + if (sourceItr != sourceMap.end()) { + result = sourceItr->second; + } else { + MOT_LOG_TRACE("Could not find in name space [%s] query: %s", queryNamespace, queryString); + } + } else { + MOT_LOG_TRACE("Could not find name space [%s] while searching for query: %s", queryNamespace, queryString); + } + return result; +} + +static void RestoreCachedPlanContexts(JitSource* jitSource) +{ + LockJitSource(jitSource); + if ((jitSource->m_codegenState == JitCodegenState::JIT_CODEGEN_READY) && + (jitSource->m_sourceJitContext != nullptr)) { + CachedPlanSource* planSource = u_sess->pcache_cxt.first_saved_plan; + while (planSource != nullptr) { + if ((planSource->mot_jit_context == nullptr) && + (strcmp(planSource->query_string, jitSource->m_queryString) == 0)) { + MOT_ASSERT(planSource->opFusionObj == NULL); + planSource->mot_jit_context = CloneJitContext(jitSource->m_sourceJitContext, JIT_CONTEXT_LOCAL); + if (planSource->mot_jit_context != nullptr) { + InvalidateJitContext(planSource->mot_jit_context, 0, JIT_CONTEXT_INVALID); + } + } + planSource = planSource->next_saved; + } + } + UnlockJitSource(jitSource); +} + +inline bool DeprecateListContainsJitSource(JitSource* jitSource) +{ + bool result = false; + JitSourceSet::iterator itr = g_jitSourceMap.m_deprecateSources.find(jitSource); + if (itr != g_jitSourceMap.m_deprecateSources.end()) { + result = true; + } + return result; +} + +static void PruneSourceMap(JitSource* jitSource, JitSourceMapType& sourceMap) +{ + MOT_LOG_TRACE("Pruning source map: %s", jitSource->m_queryString); + MOT_ASSERT(jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + JitFunctionContext* functionContext = nullptr; + if (jitSource->m_sourceJitContext != nullptr) { + functionContext = (JitFunctionContext*)jitSource->m_sourceJitContext; + } + + // for each query source in the source map, we try to locate its matching part in the source context + JitSourceMapType::iterator sourceItr = sourceMap.begin(); + while (sourceItr != sourceMap.end()) { + JitSource* querySource = sourceItr->second; + bool sourceFound = false; + if (functionContext != nullptr) { + MOT_LOG_TRACE("Searching call site for JIT source: %s", querySource->m_queryString); + for (uint32_t i = 0; i < functionContext->m_SPSubQueryCount; ++i) { + JitCallSite* callSite = &functionContext->m_SPSubQueryList[i]; + if ((callSite->m_queryContext != nullptr) && + (strcmp(callSite->m_queryContext->m_jitSource->m_queryString, querySource->m_queryString) == 0)) { + sourceFound = true; + break; + } + } + } + if (!sourceFound) { + MOT_LOG_TRACE("Pruning JIT source %u in name-space %s: %s", + querySource->m_sourceId, + jitSource->m_queryString, + querySource->m_queryString); + // we do not wish to cause revalidation in currently executing sessions, so we just mark the source + // silently as deprecate, knowing that their parent SP context is marked as invalid anyway + MarkAndAddDeprecateJitSource(querySource, true); + sourceItr = sourceMap.erase(sourceItr); + } else { + MOT_LOG_TRACE("Call site found, JIT source %u in name-space %s retained: %s", + querySource->m_sourceId, + jitSource->m_queryString, + querySource->m_queryString); + ++sourceItr; + } + } +} + +void PruneNamespace(JitSource* jitSource) +{ + MOT_LOG_TRACE("Pruning name-space of source %p: %s", jitSource, jitSource->m_queryString) + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.find(jitSource->m_queryString); + if (itr != g_jitSourceMap.m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + PruneSourceMap(jitSource, sourceMap); + } else { + MOT_LOG_TRACE("Name-space not found in global map: %s", jitSource->m_queryString); + } +} + +// Not thread-safe, caller must hold the source map lock. +static void PruneJitSourceMap() +{ + // prune map from dropped sources, and SP sources that contain unused queries + MOT_LOG_TRACE("Pruning global jit-source map"); + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.begin(); + while (itr != g_jitSourceMap.m_namespaceMap.end()) { + if (itr->first.compare(MOT_JIT_GLOBAL_QUERY_NS) != 0) { + JitSource* jitSource = GetCachedJitSource(itr->first.c_str(), true); + if (jitSource != nullptr) { + PruneNamespace(jitSource); + } + } + ++itr; + } +} + +// Not thread-safe, caller must hold the source map lock. +static void RemoveEmptyNamespaces() +{ + // prune map from empty name spaces + MOT_LOG_TRACE("Removing empty name spaces from the global JIT source map"); + JitNamespaceMapType::iterator itr = g_jitSourceMap.m_namespaceMap.begin(); + while (itr != g_jitSourceMap.m_namespaceMap.end()) { + JitSourceMapType& sourceMap = itr->second; + if (sourceMap.empty()) { + MOT_LOG_TRACE("Removing empty name-space: %s", itr->first.c_str()); + itr = g_jitSourceMap.m_namespaceMap.erase(itr); + } else { + ++itr; + } + } +} + +static void PurgeDeprecateQueryList(uint64_t relationId) +{ + LockDeprecateJitSources(); + MOT_LOG_TRACE("Purging deprecate JIT query sources by relation id: %" PRIu64, relationId); + JitSourceSet::iterator itr = g_jitSourceMap.m_deprecateSources.begin(); + while (itr != g_jitSourceMap.m_deprecateSources.end()) { + JitSource* jitSource = *itr; + uint32_t tableId = jitSource->m_tableId; + if ((jitSource->m_sourceJitContext != nullptr) && + (jitSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_QUERY)) { + tableId = (uint32_t)((JitQueryContext*)jitSource->m_sourceJitContext)->m_tableId; + } + MOT_LOG_TRACE("Checking source %p (table id: %u): %s", jitSource, tableId, jitSource->m_queryString); + LockJitSource(jitSource); + if (IsSimpleQuerySource(jitSource) && JitSourceRefersRelation(jitSource, relationId, true)) { + MOT_LOG_TRACE("Purging deprecate JIT source %p: %s", jitSource, jitSource->m_queryString); + PurgeJitSource(jitSource, relationId); + } + UnlockJitSource(jitSource); + ++itr; + } + UnlockDeprecateJitSources(); +} + +extern void MarkAndAddDeprecateJitSource(JitSource* jitSource, bool markOnly) +{ + MOT_LOG_TRACE("Trying to mark JIT source %p as deprecated and add it for cleanup (markOnly %u): %s", + jitSource, + markOnly, + jitSource->m_queryString); + bool recycleSource = false; + LockJitSource(jitSource); + DeprecateJitSource(jitSource, markOnly); + (void)CleanUpDeprecateJitSourceContexts(jitSource); + recycleSource = IsJitSourceRecyclable(jitSource); + if ((!recycleSource) && (jitSource->m_usage == JIT_CONTEXT_LOCAL)) { + // ATTENTION: Deprecated local JIT source should always be recycled directly, it should never be added to the + // deprecate list. + MOT_ASSERT(jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE); + MOT_LOG_WARN("Local %s JIT source still contains registered/deprecate JIT context objects before recycling: %s", + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_queryString); + MOT_LOG_TRACE("Local JIT source %p with state %s still contains contextList %p and deprecateContextList %p " + "before recycling: %s", + jitSource, + JitCodegenStateToString(jitSource->m_codegenState), + jitSource->m_contextList, + jitSource->m_deprecateContextList, + jitSource->m_queryString); + // NOTE: This should not happen, but in any case we should unlink all the session-local contexts before + // destroying the local JIT source. + MOT_ASSERT(0); + UnlinkLocalJitSourceContexts(jitSource); + recycleSource = true; + } + UnlockJitSource(jitSource); + + if (!recycleSource) { + LockDeprecateJitSources(); + + // Recheck for recycling after LockDeprecateJitSources() to avoid race with ScheduleDeprecateJitSourceCleanUp() + // which is called from RemoveJitSourceContext(). + LockJitSource(jitSource); + recycleSource = IsJitSourceRecyclable(jitSource); + UnlockJitSource(jitSource); + + if (!recycleSource) { + MOT_LOG_TRACE("JIT source still in use, adding to deprecate list: %s", jitSource->m_queryString); + MOT_ASSERT(jitSource->m_usage != JIT_CONTEXT_LOCAL); + MOT_ASSERT(!DeprecateListContainsJitSource(jitSource)); + if (!g_jitSourceMap.m_deprecateSources.insert(jitSource).second) { + MOT_LOG_TRACE("Failed to insert JIT source %p to deprecate list, item already exists: %s", + jitSource, + jitSource->m_queryString); + } + } + + UnlockDeprecateJitSources(); + } + + if (recycleSource) { + MOT_LOG_TRACE("Deprecate JIT source not in use, destroying: %s", jitSource->m_queryString); + DestroyJitSource(jitSource); + } +} + +static void CleanUpReadyDeprecateJitSources() +{ + LockDeprecateJitSources(); + MOT_LOG_TRACE("Cleaning up ready deprecate JIT sources"); + JitSourceSet::iterator itr = g_jitSourceMap.m_readyDeprecateSources.begin(); + while (itr != g_jitSourceMap.m_readyDeprecateSources.end()) { + JitSource* jitSource = *itr; + MOT_LOG_TRACE("Deprecate JIT source %p ready for recycling: %s", jitSource, jitSource->m_queryString); + JitSourceSet::iterator itr2 = g_jitSourceMap.m_deprecateSources.find(jitSource); + if (itr2 != g_jitSourceMap.m_deprecateSources.end()) { + (void)g_jitSourceMap.m_deprecateSources.erase(itr2); + } else { + MOT_LOG_TRACE("Could not find ready-for-recycle deprecate JIT source %p in pending list: %s", + jitSource, + jitSource->m_queryString); + } + DestroyJitSource(jitSource); + ++itr; + } + g_jitSourceMap.m_readyDeprecateSources.clear(); + UnlockDeprecateJitSources(); +} + +static void CleanUpDeprecateJitSources() +{ + LockDeprecateJitSources(); + MOT_LOG_TRACE("Cleaning up deprecate JIT sources"); + JitSourceSet::iterator itr = g_jitSourceMap.m_deprecateSources.begin(); + while (itr != g_jitSourceMap.m_deprecateSources.end()) { + JitSource* jitSource = *itr; + bool recycleSource = false; + LockJitSource(jitSource); + if ((jitSource->m_contextList == nullptr) && (jitSource->m_deprecateContextList == nullptr) && + (jitSource->m_codegenState != JitCodegenState::JIT_CODEGEN_UNAVAILABLE)) { + recycleSource = true; + } + UnlockJitSource(jitSource); + if (recycleSource) { + MOT_LOG_TRACE("Deprecate JIT source %p ready for recycling: %s", jitSource, jitSource->m_queryString); + itr = g_jitSourceMap.m_deprecateSources.erase(itr); + DestroyJitSource(jitSource); + } else { + ++itr; + } + } + UnlockDeprecateJitSources(); +} + +extern bool IsDroppedSP(Oid functionOid) +{ + bool result = false; + LockDeprecateJitSources(); + JitSPOidMapType::iterator itr = g_jitSourceMap.m_droppedSPOidMap.find(functionOid); + if (itr != g_jitSourceMap.m_droppedSPOidMap.end()) { + MOT_LOG_TRACE( + "Function %u found in dropped functions map, time: %s", functionOid, timestamptz_to_str(itr->second)); + result = true; + } + UnlockDeprecateJitSources(); + return result; +} + +extern void CleanupConcurrentlyDroppedSPSource(const char* queryString) +{ + MOT_LOG_TRACE( + "Trying to cleanup after the JIT source was dropped concurrently during compilation: %s", queryString); + + // The transaction which dropped this stored procedure (when we were compiling) should have installed + // a global source with JitCodegenState::JIT_CODEGEN_DROPPED. We can safely remove and deprecate that now. + LockJitSourceMap(); + JitSource* globalSource = FindCachedJitSource(queryString, MOT_JIT_GLOBAL_QUERY_NS, &g_jitSourceMap.m_namespaceMap); + if (globalSource != nullptr) { + LockJitSource(globalSource); + MOT_ASSERT(globalSource->m_contextType == JitContextType::JIT_CONTEXT_TYPE_FUNCTION); + bool isDropped = (globalSource->m_codegenState == JitCodegenState::JIT_CODEGEN_DROPPED) ? true : false; + Oid functionOid = globalSource->m_functionOid; + TimestampTz timestamp = globalSource->m_timestamp; + UnlockJitSource(globalSource); + if (isDropped) { + RemoveCachedGlobalJitSource(globalSource, MOT_JIT_GLOBAL_QUERY_NS); + RecordDroppedSP(functionOid, timestamp); + // Prune the dropped function source. + MOT_LOG_TRACE("Pruning dropped function source %p: %s", globalSource, globalSource->m_queryString); + PruneNamespace(globalSource); + MarkAndAddDeprecateJitSource(globalSource, false); + } + } + UnlockJitSourceMap(); } } // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source_map.h b/src/gausskernel/storage/mot/jit_exec/jit_source_map.h index e00faad26..0d06a982a 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source_map.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_source_map.h @@ -27,6 +27,8 @@ #include "jit_source.h" +#define MOT_JIT_SOURCE_MAP_USE_RWLOCK + namespace JitExec { /** * @brief Initializes the global JIT source map. @@ -43,7 +45,11 @@ extern void ClearJitSourceMap(); /** * @brief Locks the global JIT source map for synchronized access. */ +#ifdef MOT_JIT_SOURCE_MAP_USE_RWLOCK +extern void LockJitSourceMap(bool readLock = false); +#else extern void LockJitSourceMap(); +#endif /** * @brief Unlocks the global JIT source map for synchronized access. @@ -51,37 +57,111 @@ extern void LockJitSourceMap(); extern void UnlockJitSourceMap(); /** - * @brief Retrieves the umber of cached jit-source objects in the global JIT source map. */ + * @brief Retrieves the umber of cached JIT source objects in the global JIT source map. */ extern uint32_t GetJitSourceMapSize(); +/** @brief Pushed a name-space on the JIT source name-space stack for the current session.*/ +extern bool PushJitSourceNamespace(Oid functionId, const char* queryNamespace); + +/** @brief Pops a name-space from the JIT source name-space stack for the current session.*/ +extern void PopJitSourceNamespace(); + +/** @brief Retrieves the currently active name-space, or the global name-space. */ +extern const char* GetActiveNamespace(); + +/** @brief Retrieves the function name of the currently active name-space, or invalid id if none. */ +extern Oid GetActiveNamespaceFunctionId(); + /** - * @brief Retrieves a cached jit-source by its query string (not thread safe). + * @brief Retrieves a cached JIT source by its query string (not thread safe). * @param queryString The query string to search. + * @param[opt] globalNamespace Specifies whether to search in the global name-space or the active name-space. * @return The cached JIT source or NULL if none was found or an error occurred. */ -extern JitSource* GetCachedJitSource(const char* queryString); +extern JitSource* GetCachedJitSource(const char* queryString, bool globalNamespace = false); /** * @brief Adds a new JIT source to the cached source map (not thread safe). * @param cachedJitSource The cached JIT source to add. This object is expected to be empty, and serves * as a temporary stub until JIT code is fully generated. Other threads can wait for the source to be ready. + * @param[opt] globalNamespace Specifies whether to add in the global name-space or the active name-space. + * @return True if the JIT source is added successfully, otherwise false. */ -extern bool AddCachedJitSource(JitSource* cachedJitSource); +extern bool AddCachedJitSource(JitSource* cachedJitSource, bool globalNamespace = false); /** - * @brief Queries whether a ready cached jit-source exists for the given query string (thread safe). + * @brief Removes the given JIT source from the global source map (not thread safe). + * @param jitSource The JIT source to be removed. + * @param nameSpace The JIT name space this source belongs to. + */ +extern void RemoveCachedGlobalJitSource(JitSource* jitSource, const char* nameSpace); + +/** + * @brief Retrieves a cached JIT source by its query string from the global source map (not thread safe). * @param queryString The query string to search. + * @param nameSpace The JIT name space to search from. + * @return The cached JIT source or NULL if none was found or an error occurred. + */ +extern JitSource* GetCachedGlobalJitSource(const char* queryString, const char* nameSpace); + +/** + * @brief Queries whether a ready cached JIT source exists for the given query string (thread safe). + * @param queryString The query string to search. + * @param globalNamespace Specifies whether to search in the global name-space or the active name-space. + * @param allowUnavailable[opt] Specifies whether to consider unavailable source (pending compilation/revalidation to + * finish) as a ready source. * @return True if a ready JIT source exists for the given query, otherwise false. */ -extern bool ContainsReadyCachedJitSource(const char* queryString); +extern bool ContainsReadyCachedJitSource(const char* queryString, bool globalNamespace, bool allowUnavailable = false); /** - * @brief Marks all entries associated with a relation identifier as expired. - * @param relationId The envelope identifier of modified relation. - * @param purgeOnly Specifies whether to just purge all keys/indexes referring to the given relation, or should the JIT - * context also be set as expired (which triggers re-compilation of the JIT function). + * @brief Purges all entries associated with a relation or stored procedure, and optionally set them as expired (not + * thread-safe, but called from DDL, so expected not to have any race). + * @param objectId The external identifier of the relation or stored procedure that triggers the purge. + * @param purgeScope The directly affected JIT source objects. In case of JIT query source, then the object identifier + * parameter denotes a relation id, otherwise it denotes a stored procedure id. + * @param purgeAction Specifies whether to just purge all keys/indexes referring to the given relation, or should the + * JIT context also be set as expired (which triggers re-compilation of the JIT function on sub-sequent access). + * @param funcName The stored procedure name (applicable only if purgeScope is JIT_PURGE_SCOPE_SP). */ -extern void PurgeJitSourceMap(uint64_t relationId, bool purgeOnly); +extern void PurgeJitSourceMap( + uint64_t objectId, JitPurgeScope purgeScope, JitPurgeAction purgeAction, const char* funcName); + +/** @brief Get important JIT source information (thread-safe). */ +extern MotJitDetail* GetJitSourceDetail(uint32_t* num); + +/** @brief Queries whether the source map contains a function source by the given id. */ +extern bool HasJitFunctionSource(Oid functionId); + +/** @brief Revalidates a JIT source in a transactional-safe manner. */ +extern JitCodegenState RevalidateJitSourceTxn(JitSource* jitSource, TransactionId functionTxnId = InvalidTransactionId); + +/** @brief Apply current transaction changes. */ +extern void ApplyLocalJitSourceChanges(); + +/** @brief Post commit cleanup of dropped SP sources (global) in current transaction. */ +extern void PostCommitCleanupJitSources(); + +/** @brief Revert current transaction changes. */ +extern void RevertLocalJitSourceChanges(); + +/** @brief Clean up current transaction changes (no apply neither revert). */ +extern void CleanupLocalJitSourceChanges(); + +/** @brief Schedules cleanup for a deprecate JIT source that is ready to be recycled. */ +extern void ScheduleDeprecateJitSourceCleanUp(JitSource* jitSource); + +/** @brief Prunes name-space of the given JIT SP source. */ +extern void PruneNamespace(JitSource* jitSource); + +/** @brief Marks the JIT source as deprecate and adds it for cleanup. */ +extern void MarkAndAddDeprecateJitSource(JitSource* jitSource, bool markOnly); + +/** @brief Checks whether the given function Oid is recorded as dropped. */ +extern bool IsDroppedSP(Oid functionOid); + +/** @brief Performs cleanup after the JIT SP source was dropped concurrently. */ +extern void CleanupConcurrentlyDroppedSPSource(const char* queryString); } // namespace JitExec #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source_pool.cpp b/src/gausskernel/storage/mot/jit_exec/jit_source_pool.cpp index 22e926dc9..7fa26312a 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source_pool.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_source_pool.cpp @@ -25,6 +25,7 @@ #include "global.h" #include "postgres.h" #include "utils/memutils.h" +#include "storage/mot/jit_exec.h" #include "jit_source_pool.h" #include "utilities.h" @@ -57,14 +58,17 @@ struct __attribute__((packed)) JitSourcePool { uint64_t m_padding2[5]; }; -// Globals +// Global variables static JitSourcePool g_jitSourcePool __attribute__((aligned(64))) = {0}; +static bool g_isFirstBreach = true; + // forward declarations -static void FreeJitSourceArray(uint32_t count); +static void FreeJitSourceArray(); extern bool InitJitSourcePool(uint32_t poolSize) { + MOT_LOG_TRACE("Initializing global JIT source pool with size: %u", poolSize); MOT_ASSERT(g_jitSourcePool.m_sourcePool == nullptr); errno_t erc = memset_s((void*)&g_jitSourcePool, sizeof(JitSourcePool), 0, sizeof(JitSourcePool)); securec_check(erc, "\0", "\0"); @@ -85,30 +89,18 @@ extern bool InitJitSourcePool(uint32_t poolSize) "Failed to allocate %u JIT source objects (64-byte aligned %u bytes) for global JIT source pool", poolSize, allocSize); - pthread_spin_destroy(&g_jitSourcePool.m_lock); + (void)pthread_spin_destroy(&g_jitSourcePool.m_lock); return false; } erc = memset_s(g_jitSourcePool.m_sourcePool, allocSize, 0, allocSize); securec_check(erc, "\0", "\0"); - // we need now to construct each object - for (uint32_t i = 0; i < poolSize; ++i) { - JitSource* jitSource = &g_jitSourcePool.m_sourcePool[i]; - if (!InitJitSource(jitSource, "")) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "JIT Source Pool Initialization", "Failed to initialize JIT source %u", i); - // cleanup - FreeJitSourceArray(i); - pthread_spin_destroy(&g_jitSourcePool.m_lock); - return false; - } - } - // fill the free list g_jitSourcePool.m_poolSize = poolSize; for (uint32_t i = 0; i < g_jitSourcePool.m_poolSize; ++i) { JitSource* jitSource = &g_jitSourcePool.m_sourcePool[i]; - jitSource->_next = g_jitSourcePool.m_freeSourceList; + jitSource->m_sourceId = i; + jitSource->m_next = g_jitSourcePool.m_freeSourceList; g_jitSourcePool.m_freeSourceList = jitSource; } g_jitSourcePool.m_freeSourceCount = g_jitSourcePool.m_poolSize; @@ -118,11 +110,12 @@ extern bool InitJitSourcePool(uint32_t poolSize) extern void DestroyJitSourcePool() { + MOT_LOG_TRACE("Destroying global JIT source pool"); if (g_jitSourcePool.m_sourcePool == nullptr) { return; } - FreeJitSourceArray(g_jitSourcePool.m_poolSize); + FreeJitSourceArray(); int res = pthread_spin_destroy(&g_jitSourcePool.m_lock); if (res != 0) { @@ -138,8 +131,38 @@ extern void DestroyJitSourcePool() g_jitSourcePool.m_freeSourceCount = 0; } -extern JitSource* AllocPooledJitSource(const char* queryString) +/** @brief Returns a JIT source to the pool. */ +static void FreePooledJitSource(JitSource* jitSource) { + MOT_LOG_TRACE("Freeing JIT source %p", jitSource); + int res = pthread_spin_lock(&g_jitSourcePool.m_lock); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE(res, + pthread_spin_lock, + "Global JIT Source De-allocation", + "Failed to acquire spin lock for global JIT source pool"); + return; + } + + jitSource->m_next = g_jitSourcePool.m_freeSourceList; + g_jitSourcePool.m_freeSourceList = jitSource; + ++g_jitSourcePool.m_freeSourceCount; + g_isFirstBreach = true; + + res = pthread_spin_unlock(&g_jitSourcePool.m_lock); + if (res != 0) { + MOT_REPORT_SYSTEM_ERROR_CODE(res, + pthread_spin_unlock, + "Global JIT Source De-allocation", + "Failed to release spin lock for global JIT source pool"); + // system is in undefined state, we expect to crash any time soon, but we continue anyway + } +} + +/** @brief Allocates a JIT source from the pool. */ +static JitSource* AllocPooledJitSource(const char* queryString, JitContextUsage usage) +{ + bool issueWarningOnFailure = false; MOT_LOG_TRACE("Allocating JIT source for query: %s", queryString); int res = pthread_spin_lock(&g_jitSourcePool.m_lock); if (res != 0) { @@ -152,9 +175,13 @@ extern JitSource* AllocPooledJitSource(const char* queryString) JitSource* result = g_jitSourcePool.m_freeSourceList; if (g_jitSourcePool.m_freeSourceList != nullptr) { - g_jitSourcePool.m_freeSourceList = g_jitSourcePool.m_freeSourceList->_next; + g_jitSourcePool.m_freeSourceList = g_jitSourcePool.m_freeSourceList->m_next; --g_jitSourcePool.m_freeSourceCount; } + if ((result == nullptr) && g_isFirstBreach) { + issueWarningOnFailure = true; + g_isFirstBreach = false; + } res = pthread_spin_unlock(&g_jitSourcePool.m_lock); if (res != 0) { @@ -166,46 +193,68 @@ extern JitSource* AllocPooledJitSource(const char* queryString) } if (result == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, - "Global JIT Source Allocation", - "Failed to allocate JIT source, reached configured limit"); + if (issueWarningOnFailure) { + MOT_LOG_WARN("Cannot allocate JIT source, reached configured limit: %u. Consider increasing the value " + "of 'mot_codegen_limit' in mot.conf.", + GetMotCodegenLimit()); + } } else { - ReInitJitSource(result, queryString); + if (!InitJitSource(result, queryString, usage)) { + MOT_LOG_WARN("Failed to initialize JIT source"); + FreePooledJitSource(result); + result = nullptr; + } } return result; } -extern void FreePooledJitSource(JitSource* jitSource) +extern JitSource* AllocJitSource(const char* queryString, JitContextUsage usage) { - MOT_LOG_TRACE("Freeing JIT source %p with query: %s", jitSource, jitSource->_query_string); - int res = pthread_spin_lock(&g_jitSourcePool.m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(res, - pthread_spin_lock, - "Global JIT Source De-allocation", - "Failed to acquire spin lock for global JIT source pool"); - return; + if (usage == JIT_CONTEXT_GLOBAL) { + return AllocPooledJitSource(queryString, usage); } - jitSource->_next = g_jitSourcePool.m_freeSourceList; - g_jitSourcePool.m_freeSourceList = jitSource; - ++g_jitSourcePool.m_freeSourceCount; + size_t allocSize = sizeof(JitSource); + JitSource* jitSource = (JitSource*)MOT::MemSessionAlloc(allocSize); + if (jitSource == nullptr) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, + "Local JIT Source Allocation", + "Failed to allocate %" PRIu64 " bytes for local JIT source", + allocSize); + return nullptr; + } - res = pthread_spin_unlock(&g_jitSourcePool.m_lock); - if (res != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(res, - pthread_spin_unlock, - "Global JIT Source De-allocation", - "Failed to release spin lock for global JIT source pool"); - // system is in undefined state, we expect to crash any time soon, but we continue anyway + if (!InitJitSource(jitSource, queryString, usage)) { + MOT_LOG_WARN("Failed to initialize JIT source"); + MOT::MemSessionFree(jitSource); + jitSource = nullptr; + } + + return jitSource; +} + +extern void FreeJitSource(JitSource* jitSource) +{ + MOT_ASSERT(!jitSource->m_initialized); + if (jitSource->m_usage == JIT_CONTEXT_GLOBAL) { + FreePooledJitSource(jitSource); + } else { + MOT::MemSessionFree(jitSource); } } -static void FreeJitSourceArray(uint32_t count) +static void FreeJitSourceArray() { - for (uint32_t i = 0; i < count; ++i) { - DestroyJitSource(&g_jitSourcePool.m_sourcePool[i]); + MOT_LOG_TRACE("Freeing JIT source array"); + // destroy whatever is left + for (uint32_t i = 0; i < g_jitSourcePool.m_poolSize; ++i) { + JitSource* jitSource = &g_jitSourcePool.m_sourcePool[i]; + if (jitSource->m_initialized) { + MOT_LOG_WARN("Found JIT source %p id %u not destroyed yet: %s", jitSource, i, jitSource->m_queryString); + (void)CleanUpDeprecateJitSourceContexts(jitSource); + DestroyJitSource(jitSource); + } } MOT::MemGlobalFree(g_jitSourcePool.m_sourcePool); g_jitSourcePool.m_sourcePool = nullptr; diff --git a/src/gausskernel/storage/mot/jit_exec/jit_source_pool.h b/src/gausskernel/storage/mot/jit_exec/jit_source_pool.h index 2c3f1e94e..9bd145ff4 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_source_pool.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_source_pool.h @@ -39,19 +39,20 @@ extern bool InitJitSourcePool(uint32_t poolSize); extern void DestroyJitSourcePool(); /** - * @brief Allocates a JIT source from the pool. - * @param queryString The query string for which a JIT source is to be allocated. Used to - * re-initialize the pooled object. - * @return The JIT source if allocation succeeded, otherwise NULL. Consult @ref - * MOT::GetRootError() for further details. + * @brief Allocates a JIT source. + * @param queryString The query string for which a JIT source is to be allocated. Used to re-initialize the JIT source + * object. + * @param usage Specifies the usage of the source. + * @return The JIT source if allocation succeeded, otherwise NULL. Consult @ref MOT::GetRootError() for further + * details. */ -extern JitSource* AllocPooledJitSource(const char* queryString); +extern JitSource* AllocJitSource(const char* queryString, JitContextUsage usage); /** - * @brief Returns a JIT source to the pool. - * @param jitSource The JIT source to return. + * @brief Frees a JIT source. + * @param jitSource The JIT source to free. */ -extern void FreePooledJitSource(JitSource* jitSource); +extern void FreeJitSource(JitSource* jitSource); } // namespace JitExec #endif diff --git a/src/gausskernel/storage/mot/jit_exec/jit_statistics.cpp b/src/gausskernel/storage/mot/jit_exec/jit_statistics.cpp index c0401d5a5..c15a371e9 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_statistics.cpp +++ b/src/gausskernel/storage/mot/jit_exec/jit_statistics.cpp @@ -36,12 +36,14 @@ JitThreadStatistics::JitThreadStatistics(uint64_t threadId, void* inplaceBuffer) m_execQueryCount(MakeName("jit-exec", threadId).c_str()), m_invokeQueryCount(MakeName("jit-invoke", threadId).c_str()), m_execFailQueryCount(MakeName("jit-exec-fail", threadId).c_str()), - m_execAbortQueryCount(MakeName("jit-exec-abort", threadId).c_str()) + m_execAbortQueryCount(MakeName("jit-exec-abort", threadId).c_str()), + m_sessionBytes(MakeName("jit-session-bytes", threadId).c_str(), KILO_BYTE, "KB") { RegisterStatistics(&m_execQueryCount); RegisterStatistics(&m_invokeQueryCount); RegisterStatistics(&m_execFailQueryCount); RegisterStatistics(&m_execAbortQueryCount); + RegisterStatistics(&m_sessionBytes); } JitGlobalStatistics::JitGlobalStatistics(GlobalStatistics::NamingScheme namingScheme) @@ -54,7 +56,8 @@ JitGlobalStatistics::JitGlobalStatistics(GlobalStatistics::NamingScheme namingSc m_codeGenErrorQueryCount(MakeName("code-gen-error-queries", namingScheme).c_str(), 1, "queries"), m_codeCloneQueryCount(MakeName("code-clone-queries", namingScheme).c_str(), 1, "queries"), m_codeCloneErrorQueryCount(MakeName("code-clone-error-queries", namingScheme).c_str(), 1, "queries"), - m_codeExpiredQueryCount(MakeName("code-expired-queries", namingScheme).c_str(), 1, "queries") + m_codeExpiredQueryCount(MakeName("code-expired-queries", namingScheme).c_str(), 1, "queries"), + m_globalBytes(MakeName("jit-global-bytes", namingScheme).c_str(), KILO_BYTE, "KB") { RegisterStatistics(&m_jittableQueryCount); RegisterStatistics(&m_unjittableLimitQueryCount); @@ -65,6 +68,7 @@ JitGlobalStatistics::JitGlobalStatistics(GlobalStatistics::NamingScheme namingSc RegisterStatistics(&m_codeCloneQueryCount); RegisterStatistics(&m_codeCloneErrorQueryCount); RegisterStatistics(&m_codeExpiredQueryCount); + RegisterStatistics(&m_globalBytes); } MOT::TypedStatisticsGenerator JitStatisticsProvider::m_generator; @@ -76,18 +80,18 @@ JitStatisticsProvider::JitStatisticsProvider() JitStatisticsProvider::~JitStatisticsProvider() { - MOT::ConfigManager::GetInstance().RemoveConfigChangeListener(this); + (void)MOT::ConfigManager::GetInstance().RemoveConfigChangeListener(this); if (m_enable) { - MOT::StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)MOT::StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } void JitStatisticsProvider::RegisterProvider() { if (m_enable) { - MOT::StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)MOT::StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } - MOT::ConfigManager::GetInstance().AddConfigChangeListener(this); + (void)MOT::ConfigManager::GetInstance().AddConfigChangeListener(this); } bool JitStatisticsProvider::CreateInstance() @@ -135,9 +139,9 @@ void JitStatisticsProvider::OnConfigChange() if (m_enable != MOT::GetGlobalConfiguration().m_enableJitStatistics) { m_enable = MOT::GetGlobalConfiguration().m_enableJitStatistics; if (m_enable) { - MOT::StatisticsManager::GetInstance().RegisterStatisticsProvider(this); + (void)MOT::StatisticsManager::GetInstance().RegisterStatisticsProvider(this); } else { - MOT::StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); + (void)MOT::StatisticsManager::GetInstance().UnregisterStatisticsProvider(this); } } } diff --git a/src/gausskernel/storage/mot/jit_exec/jit_statistics.h b/src/gausskernel/storage/mot/jit_exec/jit_statistics.h index c379989a9..7ce4f28f1 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_statistics.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_statistics.h @@ -28,6 +28,7 @@ #include "frequency_statistic_variable.h" #include "level_statistic_variable.h" #include "numeric_statistic_variable.h" +#include "memory_statistic_variable.h" #include "iconfig_change_listener.h" #include "statistics_provider.h" #include "typed_statistics_generator.h" @@ -73,6 +74,12 @@ public: m_execAbortQueryCount.AddSample(); } + /** @var Updates the number of session-bytes allocated for JIT query execution. */ + inline void AddSessionBytes(int64_t bytes) + { + m_sessionBytes.AddSample(bytes); + } + private: /** @var The successful JIT query execution count statistic variable. */ MOT::FrequencyStatisticVariable m_execQueryCount; @@ -85,6 +92,9 @@ private: /** @var The aborted JIT query execution count statistic variable. */ MOT::FrequencyStatisticVariable m_execAbortQueryCount; + + /** @var The number of session-bytes allocated for JIT query execution. */ + MOT::MemoryStatisticVariable m_sessionBytes; }; class JitGlobalStatistics : public MOT::GlobalStatistics { @@ -148,6 +158,12 @@ public: m_codeExpiredQueryCount.AddSample(1); } + /** @var Updates the number of global-bytes allocated for JIT query execution. */ + inline void AddGlobalBytes(int64_t bytes) + { + m_globalBytes.AddSample(bytes); + } + private: MOT::LevelStatisticVariable m_jittableQueryCount; MOT::LevelStatisticVariable m_unjittableLimitQueryCount; @@ -158,6 +174,7 @@ private: MOT::LevelStatisticVariable m_codeCloneQueryCount; MOT::LevelStatisticVariable m_codeCloneErrorQueryCount; MOT::LevelStatisticVariable m_codeExpiredQueryCount; + MOT::MemoryStatisticVariable m_globalBytes; }; /** @@ -298,18 +315,36 @@ public: } } + /** @var Updates the number of session-bytes allocated for JIT query execution. */ + inline void AddSessionBytes(int64_t bytes) + { + JitThreadStatistics* jts = GetCurrentThreadStatistics(); + if (jts != nullptr) { + jts->AddSessionBytes(bytes); + } + } + + /** @var Updates the number of global-bytes allocated for JIT query execution. */ + inline void AddGlobalBytes(int64_t bytes) + { + JitGlobalStatistics* jgs = GetGlobalStatistics(); + if (jgs) { + jgs->AddGlobalBytes(bytes); + } + } + /** * @brief Derives classes should react to a notification that configuration changed. New * configuration is accessible via the ConfigManager. */ - virtual void OnConfigChange(); + void OnConfigChange() override; private: /** @brief Constructor. */ JitStatisticsProvider(); /** @brief Destructor. */ - virtual ~JitStatisticsProvider(); + ~JitStatisticsProvider() override; /** @brief Registers the provider in the manager. */ void RegisterProvider(); diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm.cpp b/src/gausskernel/storage/mot/jit_exec/jit_tvm.cpp deleted file mode 100644 index 6620665db..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm.cpp +++ /dev/null @@ -1,1071 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm.cpp - * JIT TVM (Tiny Virtual Machine). - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm.cpp - * - * ------------------------------------------------------------------------- - */ - -#include "global.h" -#include "jit_tvm.h" -#include "jit_helpers.h" -#include "mot_engine.h" -#include "utilities.h" -#include "mot_error.h" - -#include - -DECLARE_LOGGER(TVM, JitExec) - -namespace tvm { -IMPLEMENT_CLASS_LOGGER(Instruction, JitExec) -IMPLEMENT_CLASS_LOGGER(Expression, JitExec) - -extern ExecContext* allocExecContext(uint64_t register_count) -{ - size_t alloc_size = sizeof(ExecContext) + register_count * sizeof(uint64_t); - // let's use faster MOT session-local allocation - ExecContext* ctx = (ExecContext*)MOT::MemSessionAlloc(alloc_size); - if (ctx != nullptr) { - errno_t erc = memset_s(ctx, alloc_size, 0, alloc_size); - securec_check(erc, "\0", "\0"); - ctx->_register_count = register_count; - } else { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Execute JIT", - "Failed to allocate %u bytes for pseudo-jit execution context", - (unsigned)alloc_size); - } - return ctx; -} - -extern void freeExecContext(ExecContext* ctx) -{ - if (ctx != nullptr) { - MOT::MemSessionFree(ctx); - } -} - -extern void setRegisterValue(ExecContext* exec_context, int register_ref, uint64_t value) -{ - if (register_ref < (int)exec_context->_register_count) { - exec_context->_registers[register_ref] = value; - } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Execute JIT", - "Invalid register access %d exceeds limit %d", - register_ref, - (int)exec_context->_register_count); - // NOTE: should throw PG exception through report error? - // NOTE: all DirectFunction() calls should be surrounded with PG_TRY and error should be reported somehow - MOT_ASSERT(false); - } -} - -extern uint64_t getRegisterValue(ExecContext* exec_context, int register_ref) -{ - uint64_t result = 0; - if (register_ref < (int)exec_context->_register_count) { - result = exec_context->_registers[register_ref]; - } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Execute JIT", - "Invalid register access %d exceeds limit %d", - register_ref, - (int)exec_context->_register_count); - // NOTE: should throw PG exception through report error? - MOT_ASSERT(false); - } - return result; -} - -uint64_t Instruction::Exec(ExecContext* exec_context) -{ -#ifdef MOT_JIT_DEBUG - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_DEBUG)) { - MOT_LOG_DEBUG("Executing instruction:"); - Dump(); - fprintf(stderr, "\n"); - } -#endif - uint64_t value = ExecImpl(exec_context); - if (_type == Regular) { - setRegisterValue(exec_context, _register_ref, value); - } - return value; -} - -void Instruction::Dump() -{ - if (_register_ref != -1) { - fprintf(stderr, "%%%d = ", _register_ref); - } - DumpImpl(); -} - -Datum ConstExpression::eval(ExecContext* exec_context) -{ - setExprArgIsNull(_arg_pos, _is_null); - exec_context->_expr_rc = MOT_NO_ERROR; - return _datum; -} - -void ConstExpression::dump() -{ - fprintf(stderr, "%" PRIu64, (uint64_t)_datum); -} - -Datum ParamExpression::eval(ExecContext* exec_context) -{ - Datum result = PointerGetDatum(NULL); - if (_param_id < exec_context->_params->numParams) { - exec_context->_expr_rc = MOT_NO_ERROR; - result = getDatumParam(exec_context->_params, _param_id, _arg_pos); - } else { - exec_context->_expr_rc = MOT_ERROR_INDEX_OUT_OF_RANGE; - } - return result; -} - -void ParamExpression::dump() -{ - fprintf(stderr, "getDatumParam(%%params, param_id=%d, arg_pos=%d)", _param_id, _arg_pos); -} - -Datum AndExpression::eval(ExecContext* exec_context) -{ - Datum result = PointerGetDatum(NULL); - Datum lhs = _lhs->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - Datum rhs = _rhs->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = BoolGetDatum(DatumGetBool(lhs) && DatumGetBool(rhs)); - } - } - return result; -} - -void AndExpression::dump() -{ - _lhs->dump(); - fprintf(stderr, " AND "); - _rhs->dump(); -} - -Datum OrExpression::eval(ExecContext* exec_context) -{ - Datum result = PointerGetDatum(NULL); - Datum lhs = _lhs->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - Datum rhs = _rhs->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = BoolGetDatum(DatumGetBool(lhs) || DatumGetBool(rhs)); - } - } - return result; -} - -void OrExpression::dump() -{ - _lhs->dump(); - fprintf(stderr, " OR "); - _rhs->dump(); -} - -Datum NotExpression::eval(ExecContext* exec_context) -{ - Datum result = PointerGetDatum(NULL); - Datum arg = _arg->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = BoolGetDatum(!DatumGetBool(arg)); - } - return result; -} - -void NotExpression::dump() -{ - fprintf(stderr, "NOT "); - _arg->dump(); -} - -uint64_t RegisterRefInstruction::Exec(ExecContext* exec_context) -{ - return getRegisterValue(exec_context, GetRegisterRef()); -} - -void RegisterRefInstruction::Dump() -{ - fprintf(stderr, "%%%d", GetRegisterRef()); -} - -uint64_t ReturnInstruction::Exec(ExecContext* exec_context) -{ - return _return_value->Exec(exec_context); -} - -void ReturnInstruction::Dump() -{ - fprintf(stderr, "return "); - _return_value->Dump(); -} - -uint64_t ReturnNextInstruction::Exec(ExecContext* exec_context) -{ - return _return_value->Exec(exec_context); -} - -void ReturnNextInstruction::Dump() -{ - fprintf(stderr, "return next "); - _return_value->Dump(); -} - -uint64_t ConstInstruction::Exec(ExecContext* exec_context) -{ - return _value; -} - -void ConstInstruction::Dump() -{ - fprintf(stderr, "%" PRIu64, _value); -} - -ExpressionInstruction::~ExpressionInstruction() -{ - MOT_LOG_DEBUG("Deleting sub-expr %p in expression instruction %p", _expr, this); - delete _expr; -} - -uint64_t ExpressionInstruction::ExecImpl(ExecContext* exec_context) -{ - return _expr->eval(exec_context); -} - -void ExpressionInstruction::DumpImpl() -{ - _expr->dump(); -} - -uint64_t GetExpressionRCInstruction::ExecImpl(ExecContext* exec_context) -{ - return exec_context->_expr_rc; -} - -void GetExpressionRCInstruction::DumpImpl() -{ - fprintf(stderr, "getExpressionRC()"); -} - -uint64_t DebugLogInstruction::Exec(ExecContext* exec_context) -{ - debugLog(_function, _msg); - return (uint64_t)MOT::RC_OK; -} - -void DebugLogInstruction::Dump() -{ - fprintf(stderr, "debugLog(function='%s', msg='%s')", _function, _msg); -} - -void BasicBlock::setCount(int count) -{ - char id[32]; - errno_t erc = snprintf_s(id, sizeof(id), sizeof(id) - 1, "%d", count); - securec_check_ss(erc, "\0", "\0"); - _name += id; -} - -void BasicBlock::addInstruction(Instruction* instruction) -{ - _instructions.push_back(instruction); - - // update predecessors if necessary - if (instruction->GetType() == Instruction::Branch) { - AbstractBranchInstruction* branch_instruction = (AbstractBranchInstruction*)instruction; - int branch_count = branch_instruction->getBranchCount(); - for (int i = 0; i < branch_count; ++i) { - BasicBlock* block = branch_instruction->getBranchAt(i); - block->addPredecessor(this); - } - } -} - -void BasicBlock::recordRegisterReferenceDefinition(int register_index) -{ - MOT_LOG_TRACE("Adding register %d ref-def to block %s", register_index, getName()); - _register_ref_definitions.push_back(register_index); -} - -void BasicBlock::addTrivialBlockPredecessors(BasicBlock* trivial_block) -{ - // inherit all the predecessors of the trivial block - BasicBlockList::iterator block_itr = trivial_block->_predecessors.begin(); - while (block_itr != trivial_block->_predecessors.end()) { - addPredecessor(*block_itr); - ++block_itr; - } - - // and remove the trivial block itself from the list of predecessors - block_itr = find(_predecessors.begin(), _predecessors.end(), trivial_block); - MOT_ASSERT(block_itr != _predecessors.end()); - if (block_itr != _predecessors.end()) { - _predecessors.erase(block_itr); - } -} - -bool BasicBlock::isTrivial() const -{ - bool result = false; - if (_instructions.size() == 1) { - Instruction* instruction = _instructions.front(); - if (instruction->GetType() == Instruction::Branch) { - AbstractBranchInstruction* branch_instruction = (AbstractBranchInstruction*)instruction; - if (branch_instruction->getBranchType() == AbstractBranchInstruction::Unconditional) { - result = true; - } - } - } - return result; -} - -BasicBlock* BasicBlock::getNextTrivalBlock() -{ - BasicBlock* result = NULL; - MOT_ASSERT(_instructions.size() == 1); - if (_instructions.size() == 1) { - Instruction* instruction = _instructions.front(); - MOT_ASSERT(instruction->GetType() == Instruction::Branch); - if (instruction->GetType() == Instruction::Branch) { - AbstractBranchInstruction* branch_instruction = (AbstractBranchInstruction*)instruction; - MOT_ASSERT(branch_instruction->getBranchType() == AbstractBranchInstruction::Unconditional); - if (branch_instruction->getBranchType() == AbstractBranchInstruction::Unconditional) { - result = branch_instruction->getBranchAt(0); - } - } - } - return result; -} - -void BasicBlock::replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) -{ - InstructionList::iterator itr = _instructions.begin(); - while (itr != _instructions.end()) { - Instruction* instruction = *itr; - if (instruction->GetType() == Instruction::Branch) { - AbstractBranchInstruction* branch_instruction = (AbstractBranchInstruction*)instruction; - branch_instruction->replaceTrivialBlockReference(trivial_block, next_block); - } - ++itr; - } -} - -uint64_t BasicBlock::exec(ExecContext* exec_context) -{ -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Executing block: %s", getName()); -#endif - InstructionList::iterator itr = _instructions.begin(); - while (itr != _instructions.end()) { - Instruction* instruction = *itr; - uint64_t rc = instruction->Exec(exec_context); - Instruction::Type itype = instruction->GetType(); - if (itype == Instruction::Return) { - return rc; - } - if (itype == Instruction::Branch) { - BasicBlock* next_block = (BasicBlock*)(uintptr_t)rc; - return next_block->exec(exec_context); // allow tail call optimization - } - ++itr; - } - MOT_ASSERT(false); - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Execute JIT", "Reached end of block %s without terminating instruction", getName()); - return (uint64_t)MOT::RC_ERROR; -} - -uint64_t BasicBlock::execNext(ExecContext* exec_context) -{ -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Executing block %s from last point", getName()); -#endif - - // initialize instruction if required - if (!_next_instruction_valid) { - _next_instruction = _instructions.begin(); - } - - // if we already dived into the next block then continue to execute it from where it stopped - if (_current_block != nullptr) { - return _current_block->execNext(exec_context); - } - - // otherwise continue executing instructions - while (_next_instruction != _instructions.end()) { - Instruction* instruction = *_next_instruction; - ++_next_instruction; - uint64_t rc = instruction->Exec(exec_context); - - // check instruction type to understand what happened - Instruction::Type itype = instruction->GetType(); - - // case 1: function execution terminated - if (itype == Instruction::Return) { - _next_instruction_valid = false; - return rc; - } - - // case 2: function execution stopped for now, and will continue later - if (itype == Instruction::ReturnNext) { - return rc; - } - - // case 3: branch instruction leads us to another branch - if (itype == Instruction::Branch) { - _next_instruction_valid = - false; // instruction for current block will no longer be executed until function restarts - _current_block = (BasicBlock*)(uintptr_t)rc; - return _current_block->exec(exec_context); // allow tail call optimization if possible - } - } - - // guard in runtime (should not happen if function verification was correct) - MOT_ASSERT(false); - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Execute JIT", "Reached end of block %s without terminating instruction", getName()); - return (uint64_t)MOT::RC_ERROR; -} - -bool BasicBlock::endsInBranch() -{ - bool result = false; - if (!_instructions.empty()) { - Instruction::Type last_instr_type = _instructions.back()->GetType(); - result = (last_instr_type == Instruction::Branch) || (last_instr_type == Instruction::Return); - } - return result; -} - -bool BasicBlock::verify() -{ - bool result = false; - MOT_LOG_TRACE("Verifying block: %s", getName()); - if (!endsInBranch()) { - MOT_LOG_ERROR( - "Block %s is invalid: last instruction is not a terminating instruction (return or branch)", getName()); - } else { - // verify that all register references in this block are visible (i.e. are defined at this or - // a predecessor block). - if (hasIllegalInstruction()) { - MOT_LOG_ERROR( - "Block %s is invalid: Found invisible register reference (definition unreachable from current block)", - getName()); - } else { - result = true; - } - } - return result; -} - -void BasicBlock::dump() -{ - int padding = 64 - (int)_name.size(); - fprintf(stderr, "%s:%*s; preds = ", getName(), padding, ""); - - dumpPredecessors(); - fprintf(stderr, "\n"); - - dumpInstructions(); - fprintf(stderr, "\n"); -} - -void BasicBlock::dumpPredecessors() -{ - bool is_first = true; - BasicBlockList::iterator itr = _predecessors.begin(); - while (itr != _predecessors.end()) { - BasicBlock* block = *itr; - if (is_first) { - fprintf(stderr, "%%%s", block->getName()); - is_first = false; - } else { - fprintf(stderr, ", %%%s", block->getName()); - } - ++itr; - } -} - -void BasicBlock::dumpInstructions() -{ - InstructionList::iterator itr = _instructions.begin(); - while (itr != _instructions.end()) { - fprintf(stderr, " "); - (*itr)->Dump(); - fprintf(stderr, ";\n"); - ++itr; - } -} - -bool BasicBlock::hasIllegalInstruction() -{ - MOT_LOG_TRACE("Checking whether block %s has illegal instruction", getName()); - InstructionList::iterator itr = _instructions.begin(); - while (itr != _instructions.end()) { - Instruction* instruction = *itr; - if (!isInstructionLegal(instruction)) { - MOT_LOG_ERROR("Found illegal instruction:"); - instruction->Dump(); - fprintf(stderr, "\n"); - return true; - } - ++itr; - } - return false; -} - -bool BasicBlock::isInstructionLegal(Instruction* instruction) -{ - // if this is a register ref instruction then check the register is visible from this block - // otherwise check that all sub-instructions are visible - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - MOT_LOG_TRACE("Checking whether instruction is legal:"); - instruction->Dump(); - fprintf(stderr, "\n"); - } - - if (instruction->GetType() == Instruction::RegisterRef) { - BasicBlockList visited_blocks; - if (!isRegisterRefVisible(instruction->GetRegisterRef(), &visited_blocks)) { - MOT_LOG_ERROR("Register reference instruction is invisible from current block %s:", getName()); - instruction->Dump(); - fprintf(stderr, "\n"); - return false; - } - } else if (hasInvisibleRegisterRef(instruction)) { - MOT_LOG_ERROR("Found instruction with invisible register ref:"); - instruction->Dump(); - fprintf(stderr, "\n"); - return false; - } - return true; -} - -bool BasicBlock::hasInvisibleRegisterRef(Instruction* instruction) -{ - MOT_LOG_TRACE("Checking if instruction contains invisible register reference"); - int sub_inst_count = instruction->GetSubInstructionCount(); - for (int i = 0; i < sub_inst_count; ++i) { - Instruction* sub_instruction = instruction->GetSubInstructionAt(i); - if (MOT_CHECK_LOG_LEVEL(MOT::LogLevel::LL_TRACE)) { - MOT_LOG_TRACE("Checking sub-instruction:") - sub_instruction->Dump(); - fprintf(stderr, "\n"); - } - if (!isInstructionLegal(sub_instruction)) { - MOT_LOG_ERROR("Found illegal sub-instruction:"); - sub_instruction->Dump(); - fprintf(stderr, "\n"); - return true; - } - } - MOT_LOG_TRACE("Instruction does NOT contain invisible register reference"); - return false; -} - -bool BasicBlock::isRegisterRefVisible(int ref_ref_value, BasicBlockList* visited_blocks) -{ - // we search the register definition in this block and each predecessor block - // we assume that graph connectivity has been checked already (otherwise we need proper backwards DFS) - bool result = false; - bool is_first = false; - MOT_LOG_TRACE( - "Checking if register reference %d instruction is visible from current block %s", ref_ref_value, getName()); - - // avoid endless recursion - BasicBlockList::iterator itr = find(visited_blocks->begin(), visited_blocks->end(), this); - if (itr != visited_blocks->end()) { - return true; - } else { - if (visited_blocks->empty()) { - is_first = true; - } - visited_blocks->push_back(this); - } - - // check if current block contains the definition for the register index - if (containsRegRefDefinition(ref_ref_value)) { - result = true; - } else { - // other-wise search backwards in all predecessors - BasicBlockList::iterator block_itr = _predecessors.begin(); - while (block_itr != _predecessors.end()) { - BasicBlock* block = *block_itr; - if (block->isRegisterRefVisible(ref_ref_value, visited_blocks)) { - result = true; - break; - } - ++block_itr; - } - } - - // issue error only after recurrence ends - if (!result && is_first) { - MOT_LOG_ERROR("Register reference %d is invisible from current block %s", ref_ref_value, getName()); - } - return result; -} - -bool BasicBlock::containsRegRefDefinition(int ref_ref_value) -{ - bool result = false; - RegRefDefList::iterator itr = - find(_register_ref_definitions.begin(), _register_ref_definitions.end(), ref_ref_value); - if (itr != _register_ref_definitions.end()) { - result = true; - } - MOT_LOG_TRACE( - "Block %s contains register reference %d definition: %s", getName(), ref_ref_value, result ? "Yes" : "No"); - return result; -} - -uint64_t ICmpInstruction::ExecImpl(ExecContext* exec_context) -{ - uint64_t lhs_res = _lhs_instruction->Exec(exec_context); - uint64_t rhs_res = _rhs_instruction->Exec(exec_context); - return exec_cmp(lhs_res, rhs_res); -} - -void ICmpInstruction::DumpImpl() -{ - fprintf(stderr, "icmp "); - _lhs_instruction->Dump(); - - switch (_cmp_op) { - case JitExec::JIT_ICMP_EQ: - fprintf(stderr, " == "); - break; - case JitExec::JIT_ICMP_NE: - fprintf(stderr, " != "); - break; - case JitExec::JIT_ICMP_GT: - fprintf(stderr, " > "); - break; - case JitExec::JIT_ICMP_GE: - fprintf(stderr, " >= "); - break; - case JitExec::JIT_ICMP_LT: - fprintf(stderr, " < "); - break; - case JitExec::JIT_ICMP_LE: - fprintf(stderr, " <= "); - break; - default: - fprintf(stderr, " cmp? "); - } - - _rhs_instruction->Dump(); -} - -int ICmpInstruction::exec_cmp(uint64_t lhs_res, uint64_t rhs_res) -{ - switch (_cmp_op) { - case JitExec::JIT_ICMP_EQ: - return (lhs_res == rhs_res) ? 1 : 0; - case JitExec::JIT_ICMP_NE: - return (lhs_res != rhs_res) ? 1 : 0; - case JitExec::JIT_ICMP_GT: - return (lhs_res > rhs_res) ? 1 : 0; - case JitExec::JIT_ICMP_GE: - return (lhs_res >= rhs_res) ? 1 : 0; - case JitExec::JIT_ICMP_LT: - return (lhs_res < rhs_res) ? 1 : 0; - case JitExec::JIT_ICMP_LE: - return (lhs_res <= rhs_res) ? 1 : 0; - default: - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Execute Pseudo-JIT Function", "Invalid compare operator %d", (int)_cmp_op); - MOT_ASSERT(false); - return 0; - } -} - -int CondBranchInstruction::getBranchCount() -{ - return 2; -} - -BasicBlock* CondBranchInstruction::getBranchAt(int index) -{ - BasicBlock* result = NULL; - if (index == 0) { - result = _true_branch; - } else if (index == 1) { - result = _false_branch; - } else { - MOT_ASSERT(false); - } - return result; -} - -void CondBranchInstruction::replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) -{ - if (_true_branch == trivial_block) { - MOT_LOG_TRACE( - "Replacing trivial true-branch %s with next block %s", _true_branch->getName(), next_block->getName()); - _true_branch = next_block; - } - if (_false_branch == trivial_block) { - MOT_LOG_TRACE( - "Replacing trivial false-branch %s with next block %s", _false_branch->getName(), next_block->getName()); - _false_branch = next_block; - } -} - -uint64_t CondBranchInstruction::Exec(ExecContext* exec_context) -{ - BasicBlock* result = NULL; - uint64_t rc = _if_test->Exec(exec_context); - if (rc) { - result = _true_branch; - } else { - result = _false_branch; - } - return (uint64_t)result; -} - -void CondBranchInstruction::Dump() -{ - fprintf(stderr, "br "); // format compatible with IR (meaning if non-zero jump to true branch) - _if_test->Dump(); - fprintf(stderr, ", label %%%s, label %%%s", _true_branch->getName(), _false_branch->getName()); -} - -int BranchInstruction::getBranchCount() -{ - return 1; -} - -BasicBlock* BranchInstruction::getBranchAt(int index) -{ - BasicBlock* result = NULL; - if (index == 0) { - result = _target_branch; - } else { - MOT_ASSERT(false); - } - return result; -} - -void BranchInstruction::replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) -{ - if (_target_branch == trivial_block) { - MOT_LOG_TRACE( - "Replacing trivial target-branch %s with next block %s", _target_branch->getName(), next_block->getName()); - _target_branch = next_block; - } -} - -uint64_t BranchInstruction::Exec(ExecContext* exec_context) -{ - return (uint64_t)_target_branch; -} - -void BranchInstruction::Dump() -{ - fprintf(stderr, "br label %%%s", _target_branch->getName()); -} - -Function::Function(const char* function_name, const char* query_string, BasicBlock* entry_block) - : _function_name(function_name), - _query_string(query_string), - _entry_block(entry_block), - _max_register_ref(0), - _current_block(entry_block) -{ - addBlock(entry_block); -} - -Function::~Function() -{ -#ifdef MOT_JIT_DEBUG - MOT_LOG_DEBUG("Deleting function %s", _function_name.c_str()); -#endif - InstructionList::iterator instr_itr = _all_instructions.begin(); - while (instr_itr != _all_instructions.end()) { - Instruction* instruction = *instr_itr; - MOT_LOG_DEBUG("Deleting instruction %p", instruction); - delete instruction; - instr_itr = _all_instructions.erase(instr_itr); - } - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - BasicBlock* block = *block_itr; - MOT_LOG_DEBUG("Deleting block %s at %p", block->getName(), block); - delete block; - block_itr = _all_blocks.erase(block_itr); - } -} - -uint64_t Function::exec(ExecContext* exec_context) -{ - MOT_LOG_DEBUG( - "Executing TVM jitted function %s for query string: %s", _function_name.c_str(), _query_string.c_str()); - uint64_t res = _entry_block->exec(exec_context); - MOT_LOG_DEBUG("Function %s return code: %" PRIu64, _function_name.c_str(), res); - return res; -} - -uint64_t Function::execNext(ExecContext* exec_context) -{ - MOT_LOG_DEBUG("Starting/Continuing execution of pseudo-LLVM jitted function %s for query string: %s", - _function_name.c_str(), - _query_string.c_str()); - uint64_t res = _entry_block->execNext(exec_context); - MOT_LOG_DEBUG("Function %s return code: %" PRIu64, _function_name.c_str(), res); - return res; -} - -void Function::addInstruction(Instruction* instruction) -{ - _all_instructions.push_back(instruction); - if (instruction->GetRegisterRef() > _max_register_ref) { - _max_register_ref = instruction->GetRegisterRef(); - } -} - -void Function::addBlock(BasicBlock* block) -{ - _all_blocks.push_back(block); - std::pair pairib = - _block_name_count.insert(BlockNameMap::value_type(block->getName(), 0)); - if (!pairib.second) { - int count = ++pairib.first->second; - block->setCount(count); - } -} - -bool Function::finalize() -{ - // cleanup empty blocks (and in the future maybe optimize) - removeEmptyBlocks(); - rewireTrivialBlocks(); - return true; // currently no reason for error -} - -bool Function::verify() -{ - bool result = false; - MOT_LOG_TRACE("Verifying function: %s", getName()); - if (getRegisterCount() >= MOT_JIT_MAX_FUNC_REGISTERS) { - MOT_LOG_TRACE("Function %s is invalid: register count %u exceeds maximum allowed %u", - getName(), - (unsigned)getRegisterCount(), - (unsigned)MOT_JIT_MAX_FUNC_REGISTERS); - } else { - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - BasicBlock* block = *block_itr; - if (!block->verify()) { - MOT_LOG_ERROR("Function %s is invalid: Block %s verification failed", getName(), block->getName()); - return false; - } - ++block_itr; - } - result = true; - } - return result; -} - -void Function::dump() -{ - fprintf(stderr, "Function %s() {\n", getName()); - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - (*block_itr)->dump(); - ++block_itr; - } - fprintf(stderr, "}\n\n"); -} - -void Function::removeEmptyBlocks() -{ - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - BasicBlock* block = *block_itr; - if (block->isEmpty()) { - block_itr = _all_blocks.erase(block_itr); - MOT_LOG_TRACE("Function %s: removing empty block %s", getName(), block->getName()); - delete block; - } else { - ++block_itr; - } - } -} - -void Function::rewireTrivialBlocks() -{ - BasicBlock* trivial_block = findTrivialBlock(); - while (trivial_block != NULL) { - rewireTrivialBlock(trivial_block); - trivial_block = findTrivialBlock(); - } -} - -BasicBlock* Function::findTrivialBlock() -{ - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - BasicBlock* block = *block_itr; - if (block->isTrivial()) { - return block; - } - ++block_itr; - } - return NULL; -} - -void Function::rewireTrivialBlock(BasicBlock* trivial_block) -{ - // retrieve the only next block of this block - BasicBlock* next_block = trivial_block->getNextTrivalBlock(); - - // 1. fix all instructions in previous blocks referring to this block with the next block - replaceTrivialBlockReference(trivial_block, next_block); - - // 2. add to the predecessors of the next block the predecessors of this block - next_block->addTrivialBlockPredecessors(trivial_block); - - // 3. remove the block from the block list of this function - if (trivial_block == _entry_block) { - _entry_block = next_block; - } - removeTrivialBlock(trivial_block); -} - -void Function::replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) -{ - BasicBlockList::iterator block_itr = _all_blocks.begin(); - while (block_itr != _all_blocks.end()) { - BasicBlock* block = *block_itr; - if (block != trivial_block) { - block->replaceTrivialBlockReference(trivial_block, next_block); - } - ++block_itr; - } -} - -void Function::removeTrivialBlock(BasicBlock* trivial_block) -{ - BasicBlockList::iterator block_itr = find(_all_blocks.begin(), _all_blocks.end(), trivial_block); - if (block_itr != _all_blocks.end()) { - _all_blocks.erase(block_itr); - delete trivial_block; - } -} - -Builder::Builder() : _current_function(NULL), _current_block(NULL), _next_register_ref(0) -{} - -Builder::~Builder() -{} - -Function* Builder::createFunction(const char* function_name, const char* query_string) -{ - _current_block = new (std::nothrow) BasicBlock("entry"); - _current_function = new (std::nothrow) Function(function_name, query_string, _current_block); - return _current_function; -} - -Instruction* Builder::addInstruction(Instruction* instruction) -{ - Instruction* result = instruction; - if (instruction->GetType() == Instruction::Regular) { - instruction->SetRegisterRef(_next_register_ref); - result = new (std::nothrow) RegisterRefInstruction(_next_register_ref); - if (result == nullptr) { - return nullptr; - } - _current_block->recordRegisterReferenceDefinition(_next_register_ref); // for later validation - _current_function->addInstruction(result); // for cleanup only - ++_next_register_ref; - } - _current_block->addInstruction(instruction); - _current_function->addInstruction(instruction); - return result; -} - -Instruction* Builder::addExpression(Expression* expr) -{ - return addInstruction(new (std::nothrow) ExpressionInstruction(expr)); -} - -Instruction* Builder::addGetExpressionRC() -{ - return addInstruction(new (std::nothrow) GetExpressionRCInstruction()); -} - -BasicBlock* Builder::CreateBlock(const char* name) -{ - BasicBlock* block = new (std::nothrow) BasicBlock(name); - _current_function->addBlock(block); - return block; -} - -Instruction* Builder::CreateICmp(Instruction* lhs_instruction, Instruction* rhs_instruction, JitExec::JitICmpOp cmp_op) -{ - return addInstruction(new (std::nothrow) ICmpInstruction(lhs_instruction, rhs_instruction, cmp_op)); -} - -void Builder::CreateCondBr(Instruction* if_test, BasicBlock* true_branch, BasicBlock* false_branch) -{ - addInstruction(new (std::nothrow) CondBranchInstruction(if_test, true_branch, false_branch)); -} - -void Builder::CreateBr(BasicBlock* target_branch) -{ - addInstruction(new (std::nothrow) BranchInstruction(target_branch)); -} - -void Builder::SetInsertPoint(BasicBlock* block) -{ - _current_block = block; -} - -void Builder::CreateRet(Instruction* instruction) -{ - addInstruction(new (std::nothrow) ReturnInstruction(instruction)); -} - -Instruction* Builder::CreateConst(uint64_t value) -{ - Instruction* result = new (std::nothrow) ConstInstruction(value); - _current_function->addInstruction(result); - return result; -} - -BasicBlock* Builder::GetInsertBlock() -{ - return _current_block; -} - -bool Builder::currentBlockEndsInBranch() -{ - return _current_block->endsInBranch(); -} -} // namespace tvm diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm.h deleted file mode 100644 index 4d4ef2f4b..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm.h +++ /dev/null @@ -1,1395 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm.h - * JIT TVM (Tiny Virtual Machine). - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_H -#define JIT_TVM_H - -#include -#include "postgres.h" -#include "nodes/params.h" -#include "executor/tuptable.h" -#include "storage/mot/jit_def.h" -#include "debug_utils.h" -#include "logger.h" - -#include -#include -#include - -// forward declaration -namespace JitExec { -struct JitContext; -} - -namespace tvm { // Tiny Virtual Machine -class Function; -class BasicBlock; - -/**************************************** - * Execution Context and Register API - ****************************************/ -/** @struct PTVM execution context. */ -struct ExecContext { - // call parameters - /** @var The original execution context. */ - JitExec::JitContext* _jit_context; - - /** @var The parameters with which the query was invoked. */ - ParamListInfo _params; - - /** @var The result tuple, into which a result record is written. */ - TupleTableSlot* _slot; - - /** @var The number of tuples processed. */ - uint64_t* _tp_processed; - - /** @var Specifies whether a range scan ended. */ - int* _scan_ended; - - /** @var Specifies whether this is a new scan or a continued previous scan. */ - int m_newScan; - - // Pseudo-LLVM execution info - /** @var The result code of last expression evaluation. */ - int64_t _expr_rc; - - /** @var Local variable for the number of rows processed. */ - uint64_t _rows_processed; - - /** @var The number of registers used by the function. */ - uint64_t _register_count; - - /** @var The function registers. */ - uint64_t _registers[0]; -}; - -/** - * @brief Allocates an execution context for a Pseudo-LLVM function on the memory allocator of the - * current session. - * @param register_count The number of registers used by the function. - * @return The allocated context or NULL if failed. - */ -extern ExecContext* allocExecContext(uint64_t register_count); - -/** - * @brief Deallocates a pseudo-LLVM execution context. - * @param ctx The context to deallocate. - */ -extern void freeExecContext(ExecContext* ctx); - -/** - * @brief Sets a value in the register array for the given execution context. - * @param exec_context The execution context. - * @param register_ref The register index. - * @param value The value to set. - */ -extern void setRegisterValue(ExecContext* exec_context, int register_ref, uint64_t value); - -/** - * @brief Gets a value from the register array in the given execution context. - * @param exec_context The execution context. - * @param register_ref The register index. - * @return The value of the register. - */ -extern uint64_t getRegisterValue(ExecContext* exec_context, int register_ref); - -/**************************************** - * TVM Building Blocks - ****************************************/ -/*------------------------------- Instruction -------------------------------*/ -/** - * @class Instruction. An instruction is an executable object. An instruction can return a value - * that is saved in a register. - */ -class Instruction { -public: - /** @enum Instruction type. */ - enum Type { - /** @var Regular instruction returning a value. */ - Regular, - - /** @var Instruction without return value. */ - Void, - - /** @var A branch instruction (flow control). */ - Branch, - - /** @var A return statement. */ - Return, - - /** @var A register reference instruction. */ - RegisterRef, - - /** @var A return-next-tuple instruction (next function invocation continues from the same place). */ - ReturnNext - }; - - /** @brief Destructor. */ - virtual ~Instruction() - {} - - /** - * @brief Executes the instruction. - * @param execContext The execution context. - * @return The execution result, or zero if instruction does not return a value. The returned - * value is saved in the register associated with the instruction. - */ - virtual uint64_t Exec(ExecContext* execContext); - - /** @brief Retrieves the instruction type. */ - inline Type GetType() const - { - return _type; - } - - /** @brief Dumps the instruction into the standard error stream. */ - virtual void Dump(); - - /** - * @brief Sets the index of the register with which the instruction is associated. - * @param registerRef - */ - inline void SetRegisterRef(int registerRef) - { - _register_ref = registerRef; - } - - /** @brief Retrieves the index of the register with which the instruction is associated. */ - inline int GetRegisterRef() const - { - return _register_ref; - } - - /** @brief Retrieves the number of sub-instructions referred to by this instruction. */ - inline int GetSubInstructionCount() const - { - return _sub_instructions.size(); - } - - /** - * @brief Retrieves the sub-instruction of this instruction at the specified index. - * @param index The sub-instruction index. - * @return The sub-instruction. - */ - inline Instruction* GetSubInstructionAt(int index) - { - MOT_ASSERT(index < (int)_sub_instructions.size()); - return _sub_instructions[index]; - } - -protected: - /** - * @brief Constructor. - * @param type The instruction type. - */ - explicit Instruction(Type type = Regular) : _type(type), _register_ref(-1) - {} - - /** - * @brief Implements the execution of the instruction. Must be overridden by sub-classes. - * @param execContext - * @return - */ - virtual uint64_t ExecImpl(ExecContext* execContext) - { - MOT_ASSERT(false); - return 0; - } - - /** @brief Implements dumping the instruction to standard error stream. */ - virtual void DumpImpl() - {} - - /** - * @brief Adds a sub-instruction to this instruction. - * @param sub_instruction The sub-instruction to add. - * @note Every derived class who refers to sub-instruction must update the @ref Instruction - * base-class, otherwise function verification might fail, or result in unexpected runtime behavior. - */ - inline void AddSubInstruction(Instruction* sub_instruction) - { - _sub_instructions.push_back(sub_instruction); - } - - DECLARE_CLASS_LOGGER() - -private: - /** @var The instruction type. */ - Type _type; - - /** @var The register with which the instruction is associated. */ - int _register_ref; - - /** @typedef Instruction list type. */ - typedef std::vector InstructionList; - - /** @var The list of sub-instruction to which this instruction refers. */ - InstructionList _sub_instructions; -}; - -/*--------------------------------- Expression --------------------------------*/ -/** - * @class Expression. An expression is an executable object that results in a Datum value. The - * result of the execution is NOT saved in a register. - */ -class Expression { -public: - /** @brief Destructor. */ - virtual ~Expression() - {} - - /** @brief Queries whether the evaluation of the expression can fail. */ - inline bool canFail() const - { - return (_eval_result == CanFail); - } - - /** - * @brief Evaluates the expression. - * @param exec_context The execution context. - * @return The evaluation result. - */ - virtual Datum eval(ExecContext* exec_context) = 0; - - /** @brief Dumps the expression to the standard error stream. */ - virtual void dump() = 0; - -protected: - /** @enum Fallibility Constants that specify whether expression evaluation can fail. */ - enum EvalResult { - /** @var Denotes that expression evaluation can fail. */ - CanFail, - - /** @var Denotes that expression evaluation cannot fail. */ - CannotFail - }; - - /** - * @brief Constructor. - * @param eval_result Specifies whether the evaluation of the expression can fail. - */ - explicit Expression(EvalResult eval_result) : _eval_result(eval_result) - {} - - DECLARE_CLASS_LOGGER() - -private: - /** @var Specifies whether the evaluation of the expression can fail. */ - EvalResult _eval_result; -}; - -/*--------------------------------- ConstExpression --------------------------------*/ -/** @class ConstExpression. An expression denoting a constant value. */ -class ConstExpression : public Expression { -public: - /** - * @brief Constructor. - * @param datum The constant value. - * @param arg_pos The position of the expression in a compound expression. - * @param is_null Specifies whether this is a null value. - */ - ConstExpression(Datum datum, int arg_pos, int is_null) - : Expression(Expression::CannotFail), _datum(datum), _arg_pos(arg_pos), _is_null(is_null) - {} - - /** @brief Destructor. */ - ~ConstExpression() override - {} - - /** - * @brief Evaluates the constant expression. - * @param exec_context The execution context. - * @return The constant value. As a side effect the argument-is-null array in the JIT execution - * context is updated at @ref _arg_pos position according the null-status of the result. - */ - Datum eval(ExecContext* exec_context) override; - - /** @brief Dumps the expression to the standard error stream. */ - void dump() override; - -private: - /** @var The constant value. */ - Datum _datum; - - /** @var The position of the expression in a compound expression. */ - int _arg_pos; - - /** @var Specifies whether this is a null value. */ - int _is_null; -}; - -/*--------------------------------- ParamExpression --------------------------------*/ -/** @class ParamExpression. An expression that evaluates to the parameter at the specified index. */ -class ParamExpression : public Expression { -public: - /** - * @brief Constructor. - * @param param_id The zero-based index of the parameter. - * @param arg_pos The position of the expression in a compound expression. - */ - ParamExpression(int param_id, int arg_pos) : Expression(Expression::CanFail), _param_id(param_id), _arg_pos(arg_pos) - {} - - /** @brief Destructor. */ - ~ParamExpression() override - {} - - /** - * @brief Evaluates the parameter expression. - * @param exec_context The execution context. - * @return The parameter value. As a side effect the argument-is-null array in the JIT execution - * context is updated at @ref _arg_pos position according the null-status of the result. - */ - Datum eval(ExecContext* exec_context) override; - - /** @brief Dumps the expression to the standard error stream. */ - void dump() override; - -private: - /** @var The zero-based index of the parameter. */ - int _param_id; // already zero-based - - /** @var The position of the expression in a compound expression. */ - int _arg_pos; -}; - -/*--------------------------------- AndExpression --------------------------------*/ -/** @class AndExpression. An expression that evaluates to boolean AND of two sub-expressions. */ -class AndExpression : public Expression { -public: - /** - * @brief Constructor. - * @param lhs The left-hand-side sub-expression. - * @param rhs The right-hand-side sub-expression. - */ - AndExpression(Expression* lhs, Expression* rhs) : Expression(Expression::CanFail), _lhs(lhs), _rhs(rhs) - {} - - /** @brief Destructor. */ - ~AndExpression() override - {} - - /** - * @brief Evaluates the boolean expression. - * @param exec_context The execution context. - * @return The boolean result. - */ - Datum eval(ExecContext* exec_context) override; - - /** @brief Dumps the expression to the standard error stream. */ - void dump() override; - -private: - /** @var The left-hand-side sub-expression. */ - Expression* _lhs; - - /** @var The right-hand-side sub-expression. */ - Expression* _rhs; -}; - -/*--------------------------------- OrExpression --------------------------------*/ -/** @class OrExpression. An expression that evaluates to boolean OR of two sub-expressions. */ -class OrExpression : public Expression { -public: - /** - * @brief Constructor. - * @param lhs The left-hand-side sub-expression. - * @param rhs The right-hand-side sub-expression. - */ - OrExpression(Expression* lhs, Expression* rhs) : Expression(Expression::CanFail), _lhs(lhs), _rhs(rhs) - {} - - /** @brief Destructor. */ - ~OrExpression() override - {} - - /** - * @brief Evaluates the boolean expression. - * @param exec_context The execution context. - * @return The boolean result. - */ - Datum eval(ExecContext* exec_context) override; - - /** @brief Dumps the expression to the standard error stream. */ - void dump() override; - -private: - /** @var The left-hand-side sub-expression. */ - Expression* _lhs; - - /** @var The right-hand-side sub-expression. */ - Expression* _rhs; -}; - -/*--------------------------------- NotExpression --------------------------------*/ -/** @class NotExpression. An expression that evaluates to boolean NOT of a sub-expression. */ -class NotExpression : public Expression { -public: - /** - * @brief Constructor. - * @param arg The sub-expression. - */ - explicit NotExpression(Expression* arg) : Expression(Expression::CanFail), _arg(arg) - {} - - /** @brief Destructor. */ - ~NotExpression() override - {} - - /** - * @brief Evaluates the boolean expression. - * @param exec_context The execution context. - * @return The boolean result. - */ - Datum eval(ExecContext* exec_context) override; - - /** @brief Dumps the expression to the standard error stream. */ - void dump() override; - -private: - /** @var The sub-expression. */ - Expression* _arg; -}; - -/*--------------------------------- RegisterRefInstruction --------------------------------*/ -/** - * @class RegisterRefInstruction. This instruction retrieves the cached result of a previously - * executed instruction. It avoids re-execution of the instruction (which is both performance-wise - * counter-productive and semantically wrong, especially if a side-effect exists). - */ -class RegisterRefInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param register_ref The index of the register in which the cached result is found. - */ - explicit RegisterRefInstruction(int register_ref) : Instruction(Instruction::RegisterRef) - { - SetRegisterRef(register_ref); - } - - /** @brief Destructor. */ - ~RegisterRefInstruction() override - {} - - /** - * @brief Executes the instruction by the retrieving the cached result from the appropriate register. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is NOT saved in any register (as it is by itself being retrieved from - * a register). - */ - uint64_t Exec(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; -}; - -/*--------------------------------- ReturnInstruction --------------------------------*/ -/** @class ReturnInstruction. An instruction that ends the execution of the function. */ -class ReturnInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param return_value The return value instruction. - */ - explicit ReturnInstruction(Instruction* return_value) - : Instruction(Instruction::Return), _return_value(return_value) - { - AddSubInstruction(return_value); - } - - /** @brief Destructor. */ - ~ReturnInstruction() override - { - _return_value = nullptr; - } - - /** - * @brief Executes the instruction by evaluating the sub-instruction and returning the resulting - * value. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is saved in NOT saved in any register, since it is executed only once - * (although the underlying sub-instruction could be very well a @ref RegisterRefInstruction - * instruction). - */ - uint64_t Exec(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; - -private: - /** @brief The sub-instruction that should be evaluated as the return value. */ - Instruction* _return_value; -}; - -/*--------------------------------- ReturnNextInstruction --------------------------------*/ -/** - * @class ReturnNextInstruction. An instruction that returns a value but does not end the execution - * of the function. - */ -class ReturnNextInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param return_value The return value instruction. - */ - ReturnNextInstruction(Instruction* return_value) : Instruction(Instruction::ReturnNext), _return_value(return_value) - { - AddSubInstruction(return_value); - } - - /** @brief Destructor. */ - ~ReturnNextInstruction() override - { - _return_value = nullptr; - } - - /** - * @brief Executes the instruction by evaluating the sub-instruction and returning the resulting - * value. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is saved in NOT saved in any register, since it is executed only once - * (although the underlying sub-instruction could be very well a @ref RegisterRefInstruction - * instruction). - */ - uint64_t Exec(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; - -private: - /** @brief The sub-instruction that should be evaluated as the return value. */ - Instruction* _return_value; -}; - -/*--------------------------------- ConstInstruction --------------------------------*/ -/** - * @class ConstInstruction. A instruction that evaluates to a constant value. This kind of - * instruction is required in cases where an instruction is required (and therefore a @ref - * ConstExpression cannot be used). - */ -class ConstInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param value The constant value. - */ - explicit ConstInstruction(uint64_t value) : Instruction(Instruction::Void), _value(value) - {} - - /** @brief Destructor. */ - ~ConstInstruction() override - {} - - /** - * @brief Executes the instruction by returning the constant value. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is NOT saved in any register. - */ - uint64_t Exec(ExecContext* exec_cContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; - -private: - /** @var The constant value. */ - uint64_t _value; -}; - -/*--------------------------------- ExpressionInstruction --------------------------------*/ -/** - * @class ExpressionInstruction. A wrapper for @ref Expression. The result is cached in a register, - * along with a RegisterRefInstruction as a result. This ensures the expression is executed only - * once, as well as in cases where an @ref Instruction is required and @ref Expression cannot be used. - */ -class ExpressionInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param expr The sub-expression. - */ - explicit ExpressionInstruction(Expression* expr) : _expr(expr) - {} - - /** @brief Destructor. */ - ~ExpressionInstruction() override; - -protected: - /** - * @brief Implements the execution of the instruction by evaluating the sub-expression. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is saved in a register. - */ - uint64_t ExecImpl(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void DumpImpl() override; - -private: - /** @var The sub-expression. */ - Expression* _expr; -}; - -/*--------------------------------- GetExpressionRCInstruction --------------------------------*/ -/** - * @class GetExpressionRCInstruction. An instruction used for retrieving the execution result of the - * recently evaluated expression. Since expressions return a Datum value, there is no way to tell - * whether execution succeeded or failed. Each expression can communicate its execution success - * status through the execution context. This instruction retrieves the execution result of the - * recently evaluated expression. - */ -class GetExpressionRCInstruction : public Instruction { -public: - /** @brief Constructor. */ - GetExpressionRCInstruction() - {} - - /** @brief Destructor. */ - ~GetExpressionRCInstruction() override - {} - -protected: - /** - * @brief Implements the execution of the instruction by retrieving the execution result of the - * recently evaluated expression from the execution context. - * @param execContext The execution context. - * @return The execution result. - * @note The returned value is saved in a register, since a subsequent expression evaluation will - * override the recent expression evaluation execution result in the execution context. - */ - uint64_t ExecImpl(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void DumpImpl() override; -}; - -/*--------------------------------- DebugLogInstruction --------------------------------*/ -/** @class DebugLogInstruction. Issues a debug log message. */ -class DebugLogInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param function The issuing function. - * @param msg The debug log message. - */ - DebugLogInstruction(const char* function, const char* msg) - : Instruction(Instruction::Void), _function(function), _msg(msg) - {} - - /** @brief Destructor. */ - ~DebugLogInstruction() override - { - _function = nullptr; - _msg = nullptr; - } - - /** - * @brief Executes the instruction by printing the debug message. - * @param execContext The execution context. - * @return Not used. - */ - uint64_t Exec(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; - -private: - /** @brief The issuing function. */ - const char* _function; - - /** @var The debug log message. */ - const char* _msg; -}; - -/*--------------------------------- AbstractBranchInstruction --------------------------------*/ -/** @class AbstractBranchInstruction. The parent class for all branch instruction. */ -class AbstractBranchInstruction : public Instruction { -public: - /** @enum The branch type. */ - enum BranchType { - /** @var This is a condition branch. */ - Conditional, - - /** @var This is an unconditional branch. */ - Unconditional - }; - - /** @brief Retrieves the branch instruction type. */ - inline BranchType getBranchType() const - { - return _branch_type; - } - - /** @brief Retrieves the number of sub-branches in this branch instruction. */ - virtual int getBranchCount() = 0; - - /** @brief Retrieves the execution block associated with the branch at the specified index. */ - virtual BasicBlock* getBranchAt(int index) = 0; - - /** - * @brief Assistance in function finalization. Replaces a trivial block in this branch - * instruction with the next block (in case this branch instruction refers to the trivial block). - * @param trivial_block The trivial block to replace. - * @param next_block The next block to use instead. - * @note A trivial block is a block with only one unconditional branch instruction. - */ - virtual void replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) = 0; - - /** @brief Destructor. */ - ~AbstractBranchInstruction() override - {} - -protected: - /** - * @brief Constructor. - * @param branch_type The branch instruction type. - */ - explicit AbstractBranchInstruction(BranchType branch_type) - : Instruction(Instruction::Branch), _branch_type(branch_type) - {} - -private: - /** @var The branch instruction type. */ - BranchType _branch_type; -}; - -/*--------------------------------- BasicBlock --------------------------------*/ -/** @class BasicBlock. Represents a labeled block of instructions. */ -class BasicBlock { -public: - /** - * @brief Constructor. - * @param name The name of the block. - */ - explicit BasicBlock(const char* name) : _name(name), _next_instruction_valid(false), _current_block(nullptr) - {} - - /** @brief Destructor. */ - ~BasicBlock() - { - _current_block = nullptr; - } - - /** - * @brief Sets the block count. Used for producing a unique block name, in case more than one - * block has the same name. - * @param count The count value. - */ - void setCount(int count); - - /** @brief Retrieves the name of the block. */ - inline const char* getName() const - { - return _name.c_str(); - } - - /** - * @brief Adds an instruction to the block. - * @param instruction The instruction to add. - */ - void addInstruction(Instruction* instruction); - - /** - * @brief Records a register reference definition (required for function validation). - * @param register_index The register index. - */ - void recordRegisterReferenceDefinition(int register_index); - - /** @brief Queries whether this block is empty. */ - inline bool isEmpty() const - { - return _instructions.empty(); - } - - /** - * @brief Explicitly adds a predecessor to this block. - * @param block The predecessor block to add. - */ - inline void addPredecessor(BasicBlock* block) - { - _predecessors.push_back(block); - } - - /** - * @brief Adds to this block all the predecessors of the given trivial block. - * @param trivial_block The trivial block. - * @note This is the block that replaces a trivial block, therefore it needs to "inherit" all the - * blocks preceeding the trivial block. The trivial block will subsequently be removed and destroyed. - */ - void addTrivialBlockPredecessors(BasicBlock* trivial_block); - - /** - * @brief Queries whether this block is trivial (i.e. contains only one unconditional branch - * instruction. - */ - bool isTrivial() const; - - /** - * @brief Retrieves the next of this block, assuming this block is trivial. - * @return The next block if this is a trivial block, otherwise null. - */ - BasicBlock* getNextTrivalBlock(); - - /** - * @brief Replaces in all block instructions, all references to the trivial block with the next - * block. - * @param trivial_block The trivial block to replace. - * @param next_block The next block to use instead. - */ - void replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block); - - /** - * @brief Executes the instruction block. - * @param exec_context The execution context. - * @return The result of the first return statement encountered in this or any inner block. - */ - uint64_t exec(ExecContext* exec_context); - - /** - * @brief Starts or continues executes of the instruction block form the last point it stopped. - * @param exec_context The execution context. - * @return The result of the first return statement encountered in this or any inner block. - */ - uint64_t execNext(ExecContext* exec_context); - - /** @brief Queries whether this block ends in a branch instruction. */ - bool endsInBranch(); - - /** @brief Verifies the validity of the instruction block. */ - bool verify(); - - /** @brief Dumps the instruction block to the standard error stream. */ - void dump(); - -private: - /** @brief The block name. */ - std::string _name; - - /** @typedef Instruction list type. */ - typedef std::list InstructionList; - - /** @typedef BasicBlock list type. */ - typedef std::list BasicBlockList; - - /** @typedef Register reference definition list type. */ - typedef std::list RegRefDefList; - - /** @var The instructions of the block. */ - InstructionList _instructions; - - /** - * @var The blocks preceeding this block (i.e. that branch into this block). The predecessor - * block list is used to validate the function. - */ - BasicBlockList _predecessors; - - /** - * @var The list of registers referenced by instructions in this block. This list is used for - * validating the function. - */ - RegRefDefList _register_ref_definitions; - - /** @var The next instruction to execute in continued execution mode. */ - InstructionList::iterator _next_instruction; - - /** @var Specifies whether the @ref _next_instruction member variable is valid. */ - bool _next_instruction_valid; - - /** @var The current block being executed in continued execution mode. */ - BasicBlock* _current_block; - - /** @brief Dumps the list of predecessors. */ - void dumpPredecessors(); - - /** @brief Dumps the list of instructions. */ - void dumpInstructions(); - - /** - * @brief Queries whether the block contains an instruction that refers to a register invisible - * from this block. This can happen if the register is initialized in a block that does not - * precede this block. In such a case, referring to the register refers to uninitialized memory, - * which would normally lead to crash. - */ - bool hasIllegalInstruction(); - - /** - * @brief Queries whether all registers referred to by the instruction are visible. - * @param instruction The instruction to validate. - * @return True if the instruction is valid. - */ - bool isInstructionLegal(Instruction* instruction); - - /** - * Queries whether an instruction has some invisible sub-instruction. - * @param instruction The instruction to examine. - * @return True if a visibility problem was detected with any sub-instruction. - */ - bool hasInvisibleRegisterRef(Instruction* instruction); - - /** - * @brief Queries whether a register reference is visible. All preceeding blocks are scanned in a - * DFS manner. - * @param ref_ref_value The register index. - * @param visited_blocks The blocks already visited during this scan (avoid endless recurrence). - * @return True if the register definition resides in a block preceeding to this one, otherwise false. - */ - bool isRegisterRefVisible(int ref_ref_value, BasicBlockList* visited_blocks); - - /** - * @brief Queries whether this block contains a specific register definition instruction. - * @param ref_ref_value The register index. - * @return True if the block contains the register definition, otherwise false. - */ - bool containsRegRefDefinition(int ref_ref_value); -}; - -/*--------------------------------- ICmpInstruction --------------------------------*/ -/** @class ICmpEQInstruction. An instruction to compare two values. */ -class ICmpInstruction : public Instruction { -public: - /** - * @brief Constructor. - * @param lhs_instruction The left-hand side operand. - * @param rhs_instruction The right-hand side operand. - * @param cmp_op The comparison operator. - */ - ICmpInstruction(Instruction* lhs_instruction, Instruction* rhs_instruction, JitExec::JitICmpOp cmp_op) - : _lhs_instruction(lhs_instruction), _rhs_instruction(rhs_instruction), _cmp_op(cmp_op) - {} - - /** @brief Destructor. */ - ~ICmpInstruction() override - { - _lhs_instruction = nullptr; - _rhs_instruction = nullptr; - } - -protected: - /** - * @brief Implements the execution of the instruction. - * @param execContext The execution context. - * @return Non-zero value if the comparison resulted in true, otherwise zero. - */ - uint64_t ExecImpl(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void DumpImpl() override; - -private: - /** @var The left-hand side operand. */ - Instruction* _lhs_instruction; - - /** @var The right-hand side operand. */ - Instruction* _rhs_instruction; - - /** @var The comparison operator. */ - JitExec::JitICmpOp _cmp_op; - - /** - * @brief Executes the comparison between two integer values. - * @param lhs_res The left-hand size value. - * @param rhs_res The right-hand size value. - * @return Non-zero value if the comparison resulted in true, otherwise zero. - */ - int exec_cmp(uint64_t lhs_res, uint64_t rhs_res); -}; - -/*--------------------------------- CondBranchInstruction --------------------------------*/ -/** @class CondBranchInstruction. Conditional branch instruction. */ -class CondBranchInstruction : public AbstractBranchInstruction { -public: - /** - * @brief Constructor. - * @param if_test The condition to test. - * @param true_branch The block to execute if the condition evaluated to true. - * @param false_branch The block to execute if the condition evaluated to false. - */ - CondBranchInstruction(Instruction* if_test, BasicBlock* true_branch, BasicBlock* false_branch) - : AbstractBranchInstruction(AbstractBranchInstruction::Conditional), - _if_test(if_test), - _true_branch(true_branch), - _false_branch(false_branch) - {} - - /** @brief Destructor. */ - ~CondBranchInstruction() override - { - _if_test = nullptr; - _true_branch = nullptr; - _false_branch = nullptr; - } - - /** @brief Implements base class. Conditional branch instruction has two outcome branches. */ - int getBranchCount() override; - - /** - * @brief Implements base class. At index 0 is the true branch, and at index 1 is the false branch. - * @param index The branch index. - * @return The requested branch. - */ - BasicBlock* getBranchAt(int index) override; - - /** - * @brief Assistance in function finalization. Replaces a trivial block in this branch - * instruction with the next block (in case this branch instruction refers to the trivial block). - * @param trivial_block The trivial block to replace. - * @param next_block The next block to use instead. - * @note A trivial block is a block with only one unconditional branch instruction. - */ - void replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) override; - - /** - * @brief Executes the instruction. Evaluates the condition and returns the next block to execute. - * @param execContext The execution context. - * @return The next block to execute. - */ - uint64_t Exec(ExecContext* execContext) override; - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() override; - -private: - /** @var The condition to test. */ - Instruction* _if_test; - - /** @var The block to execute if the condition evaluated to true. */ - BasicBlock* _true_branch; - - /** @var The block to execute if the condition evaluated to false. */ - BasicBlock* _false_branch; -}; - -/*--------------------------------- BranchInstruction --------------------------------*/ -/** @class BranchInstruction. Unconditional branch instruction. */ -class BranchInstruction : public AbstractBranchInstruction { -public: - /** - * @brief Constructor. - * @param target_branch The next block to execute. - */ - explicit BranchInstruction(BasicBlock* target_branch) - : AbstractBranchInstruction(AbstractBranchInstruction::Unconditional), _target_branch(target_branch) - {} - - /** @brief Destructor. */ - ~BranchInstruction() override - { - _target_branch = nullptr; - } - - /** @brief Implements base class. Unconditional branch instruction has only one outcome branch. */ - int getBranchCount() override; - - /** - * @brief Implements base class. At index 0 is the target branch. - * @param index The branch index. - * @return The requested branch. - */ - BasicBlock* getBranchAt(int index) override; - - /** - * @brief Assistance in function finalization. Replaces a trivial block in this branch - * instruction with the next block (in case this branch instruction refers to the trivial block). - * @param trivial_block The trivial block to replace. - * @param next_block The next block to use instead. - * @note A trivial block is a block with only one unconditional branch instruction. - */ - void replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block) override; - - /** - * @brief Executes the instruction. Returns the next block to execute. - * @param exec_context The execution context. - * @return The next block to execute. - */ - uint64_t Exec(ExecContext* exec_context) override; - - /** @brief Dumps the block to the standard error stream. */ - void Dump() override; - -private: - /** @var The next block to execute. */ - BasicBlock* _target_branch; -}; - -/*--------------------------------- TVM Function --------------------------------*/ -/** @class Function. A Pseudo-LLVM function. */ -class Function { -public: - /** - * @brief Constructor. - * @param function_name The name of the function. - * @param query_string The query/function string being executed. - * @param entry_block The first block to execute. - */ - Function(const char* function_name, const char* query_string, BasicBlock* entry_block); - - /** @brief Destructor. */ - ~Function(); - - /** @brief Retrieves the name of the function. */ - inline const char* getName() const - { - return _function_name.c_str(); - } - - /** @brief Retrieves the maximum number of registers used by the function. */ - inline uint64_t getRegisterCount() const - { - return _max_register_ref + 1; - } - - /** - * @brief Executes the function. - * @param exec_context The execution context. - * @return The value evaluated by the first return instruction encountered. - */ - uint64_t exec(ExecContext* exec_context); - - /** - * @brief Executes the function up to the next "Return-Next" instruction or "Return" instruction. - * @param exec_context The execution context. - * @return The value evaluated by the first return instruction encountered. - * @note This is used to assist in executing efficiently "Return Next" in stored procedures. - */ - uint64_t execNext(ExecContext* exec_context); - - /** - * @brief Adds instruction to the function. This is in addition to adding the instruction to its - * containing block. - * @param instruction The instruction to add. - */ - void addInstruction(Instruction* instruction); - - /** - * @brief Adds a block to the function. - * @param block The block to add. - */ - void addBlock(BasicBlock* block); - - /** - * @brief Finalizes the function. - * @return True if finalization finished successfully, otherwise false. - */ - bool finalize(); - - /** @brief Verifies the validity of the function. */ - bool verify(); - - /** @brief Dumps the block to the standard error stream. */ - void dump(); - -private: - /** @typedef Block name map type. */ - typedef std::map BlockNameMap; - - /** @typedef Instruction list type. */ - typedef std::list InstructionList; - - /** @typedef Block list type. */ - typedef std::list BasicBlockList; - - /** @var The function name. */ - std::string _function_name; - - /** @var The query/function string being executed. */ - std::string _query_string; - - /** @var The first block to execute. */ - BasicBlock* _entry_block; - - /** @var A map to count the number of times each block name was used. */ - BlockNameMap _block_name_count; - - /** @var The maximum number of registers used by the function. */ - int _max_register_ref; - - /** @var The list of all instructions used by all blocks in this function. */ - InstructionList _all_instructions; - - /** @var The list of all blocks used by this function. */ - BasicBlockList _all_blocks; - - /** @var The currently executing block. */ - BasicBlock* _current_block; - - /** @brief Removes all empty blocks from execution. */ - void removeEmptyBlocks(); - - /** @brief Remove all trivial blocks (and rewire all predecessors to the next block). */ - void rewireTrivialBlocks(); - - /** @brief Searches for a trivial block. */ - BasicBlock* findTrivialBlock(); - - /** @brief Requires a trivial block. */ - void rewireTrivialBlock(BasicBlock* trivial_block); - - /** - * @brief Replaces all references to a trivial block with its next block. - * @param trivial_block The trivial block. - * @param next_block The next block to use instead. - */ - void replaceTrivialBlockReference(BasicBlock* trivial_block, BasicBlock* next_block); - - /** - * @brief Removes and destroys a trivial block. - * @param trivial_block The trivial block to cleanup. - */ - void removeTrivialBlock(BasicBlock* trivial_block); -}; - -/*--------------------------------- TVM Builder --------------------------------*/ -/** @class Builder. Helper class to build Pseudo-LLVM functions. */ -class Builder { -public: - /** @brief Constructor. */ - Builder(); - - /** @brief Destructor. */ - ~Builder(); - - /** - * @brief Creates a function. This is the first step to do. - * @param functionName The name of the function. - * @param queryString The string of the query/function it executes. - * @return The resulting object or NULL if allocation failed. - * @note The function is initialized with an initial empty entry blocks. New instructions will be - * added to the entry block until a new block is started. - */ - Function* createFunction(const char* functionName, const char* queryString); - - /** - * @brief Adds an instruction to the current block. - * @param instruction The instruction to add. - * @return The resulting instruction. - * @note If this is a regular instruction then a @ref RegisterRefInstruction is created for it and - * returned instead, so that all other instructions using it will access in runtime the cached - * result of the original instruction. - */ - Instruction* addInstruction(Instruction* instruction); - - /** - * @brief Adds an @ref ExpressionInstruction for the given expression. - * @param expr The expression to add. - * @return The resulting instruction. This is actually a @ref RegisterRefInstruction instruction - * pointing to the result of the original @ref ExpressionInstruction. - */ - Instruction* addExpression(Expression* expr); - - /** - * @brief Adds a @ref GetExpressionRCInstruction to retrieve the execution result of the recently - * evaluated expression. - * @return The resulting instruction. No @ref RegisterRefInstruction is produced. - */ - Instruction* addGetExpressionRC(); - - /** - * @brief Creates a new block of execution. The current block is not replaced yet. In order to do - * so, call @ref SetInsertPoint(). - * @param name The name of the block. If this name was used in the past, then an integer suffix - * will be added to it to ensure uniqueness. - * @return The new block. - */ - BasicBlock* CreateBlock(const char* name); - - /** - * @brief Creates an integer comparison instruction. - * @param lhsInstruction The left-hand side operand. - * @param rhsInstruction The right-hand side operand. - * @param cmpOp The comparison operator. - * @return The resulting instruction. This is a @ref RegisterRefInstruction to the cached result - * of the original instruction. - */ - Instruction* CreateICmp(Instruction* lhsInstruction, Instruction* rhsInstruction, JitExec::JitICmpOp cmpOp); - - /** - * @brief Creates an integer EQUALS comparison instruction. - * @param lhs_instruction The left-hand side operand. - * @param rhs_instruction The right-hand side operand. - * @return The resulting instruction. This is a @ref RegisterRefInstruction to the cached result - * of the original instruction. - */ - inline Instruction* CreateICmpEQ(Instruction* lhs_instruction, Instruction* rhs_instruction) - { - return CreateICmp(lhs_instruction, rhs_instruction, JitExec::JIT_ICMP_EQ); - } - - /** - * @brief Creates (and adds to the current block) a conditional branch instruction. - * @param if_test The condition test instruction. - * @param true_branch The block to execute if the condition evaluates to true. - * @param false_branch The block to execute if the condition evaluates to false. - */ - void CreateCondBr(Instruction* if_test, BasicBlock* true_branch, BasicBlock* false_branch); - - /** - * @brief Creates (and adds to the current block) an unconditional branch instruction. - * @param target_branch The next block to execute. - */ - void CreateBr(BasicBlock* target_branch); - - /** - * @brief Sets the current block into which new instructions are added. - * @param block The block to use. - */ - void SetInsertPoint(BasicBlock* block); - - /** - * @brief Creates (and adds to the current block) a return statement. - * @param return_value The instruction to be evaluated as the return value. - */ - void CreateRet(Instruction* return_value); - - /** - * @brief Creates a @ref ConstInstruction. The instruction is not added to the current block as it - * has no effect. - * @param value The constant value. - * @return The resulting instruction. - */ - Instruction* CreateConst(uint64_t value); - - /** @brief Retrieves the currently built block. */ - BasicBlock* GetInsertBlock(); - - /** @brief Queries whether the current block ends with a branch instruction. */ - bool currentBlockEndsInBranch(); - -private: - /** @var The function being built. */ - Function* _current_function; - - /** @var The current block into which instructions are added. */ - BasicBlock* _current_block; - - /** @var The index of the next register to use. */ - int _next_register_ref; -}; -} // namespace tvm - -#endif /* JIT_TVM_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.cpp b/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.cpp deleted file mode 100644 index 654fc3ba3..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.cpp +++ /dev/null @@ -1,2240 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_blocks.cpp - * Helpers to generate compound TVM-jitted code. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.cpp - * - * ------------------------------------------------------------------------- - */ - -/* - * ATTENTION: Be sure to include jit_tvm_query.h before anything else because of libintl.h - * (jit_tvm_blocks.h includes jit_tvm_query.h before anything else). - * See jit_tvm_query.h for more details. - */ -#include "jit_tvm_blocks.h" -#include "jit_tvm_funcs.h" -#include "jit_tvm_util.h" -#include "jit_util.h" -#include "mot_error.h" -#include "utilities.h" - -#include "catalog/pg_aggregate.h" - -using namespace tvm; - -namespace JitExec { -DECLARE_LOGGER(JitTvmBlocks, JitExec) - -static bool ProcessJoinOpExpr( - JitTvmCodeGenContext* ctx, const OpExpr* op_expr, int* column_count, int* column_array, int* max_arg); -static bool ProcessJoinBoolExpr( - JitTvmCodeGenContext* ctx, const BoolExpr* boolexpr, int* column_count, int* column_array, int* max_arg); -static Instruction* buildExpression(JitTvmCodeGenContext* ctx, Expression* expr); -static Expression* ProcessExpr( - JitTvmCodeGenContext* ctx, Expr* expr, int& result_type, int arg_pos, int depth, int* max_arg); -static Expression* ProcessFilterExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitFilter* filter, int* max_arg); -static Expression* ProcessExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitExpr* expr, int* max_arg); - -void CreateJittedFunction(JitTvmCodeGenContext* ctx, const char* function_name, const char* query_string) -{ - ctx->m_jittedQuery = ctx->_builder->createFunction(function_name, query_string); - IssueDebugLog("Starting execution of jitted function"); -} - -static bool ProcessJoinExpr(JitTvmCodeGenContext* ctx, Expr* expr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - if (expr->type == T_OpExpr) { - result = ProcessJoinOpExpr(ctx, (OpExpr*)expr, column_count, column_array, max_arg); - } else if (expr->type == T_BoolExpr) { - result = ProcessJoinBoolExpr(ctx, (BoolExpr*)expr, column_count, column_array, max_arg); - } else { - MOT_LOG_TRACE("Unsupported expression type %d while processing Join Expr", (int)expr->type); - } - return result; -} - -static bool ProcessJoinOpExpr( - JitTvmCodeGenContext* ctx, const OpExpr* op_expr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - - // process only point queries - if (IsWhereOperatorSupported(op_expr->opno)) { - Instruction* value = nullptr; - Expression* value_expr = nullptr; - ListCell* lc1 = nullptr; - int colid = -1; - int vartype = -1; - int result_type = -1; - - foreach (lc1, op_expr->args) { - Expr* expr = (Expr*)lfirst(lc1); - if (expr->type == - T_RelabelType) { // sometimes relabel expression hides the inner expression, so we peel it off - expr = ((RelabelType*)expr)->arg; - } - if (expr->type == T_Var) { - Var* var = (Var*)expr; - colid = var->varattno; - vartype = var->vartype; - if (!IsTypeSupported(vartype)) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported type %d", vartype); - return false; - } - // no further processing - } else { - value_expr = ProcessExpr(ctx, expr, result_type, 0, 0, max_arg); - if (value_expr == nullptr) { - MOT_LOG_TRACE("Unsupported operand type %d while processing Join OpExpr", (int)expr->type); - } else if (!IsTypeSupported(result_type)) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported result type %d", result_type); - } else { - value = buildExpression(ctx, value_expr); - } - break; - } - } - - if ((colid != -1) && (value != nullptr) && (vartype != -1) && (result_type != -1)) { - if (result_type != vartype) { - MOT_LOG_TRACE("ProcessJoinOpExpr(): vartype %d and result-type %d mismatch", vartype, result_type); - return false; - } - // execute: column = getColumnAt(colid) - Instruction* column = AddGetColumnAt(ctx, - colid, - JIT_RANGE_SCAN_MAIN); // no need to translate to zero-based index (first column is null bits) - int index_colid = ctx->_table_info.m_columnMap[colid]; - AddBuildDatumKey(ctx, column, index_colid, value, vartype, JIT_RANGE_ITERATOR_START, JIT_RANGE_SCAN_MAIN); - - MOT_LOG_DEBUG("Encountered table column %d, index column %d in where clause", colid, index_colid); - ++(*column_count); - column_array[index_colid] = 1; - result = true; - } else { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Invalid expression (colid=%d, value=%p, vartype=%d, result_type=%d)", - colid, - value, - vartype, - result_type); - } - } else { - MOT_LOG_TRACE("ProcessJoinOpExpr(): Unsupported operator type %u", op_expr->opno); - } - - return result; -} - -static bool ProcessJoinBoolExpr( - JitTvmCodeGenContext* ctx, const BoolExpr* boolexpr, int* column_count, int* column_array, int* max_arg) -{ - bool result = false; - if (boolexpr->boolop == AND_EXPR) { - // now traverse args to get param index to build search key - ListCell* lc = nullptr; - foreach (lc, boolexpr->args) { - // each element is Expr - Expr* expr = (Expr*)lfirst(lc); - result = ProcessJoinExpr(ctx, expr, column_count, column_array, max_arg); - if (!result) { - MOT_LOG_TRACE("Failed to process operand while processing Join BoolExpr"); - break; - } - } - } else { - MOT_LOG_TRACE("Unsupported bool operation %d while processing Join BoolExpr", (int)boolexpr->boolop); - } - return result; -} - -#ifdef DEFINE_BLOCK -#undef DEFINE_BLOCK -#endif -#define DEFINE_BLOCK(block_name, unused) BasicBlock* block_name = ctx->_builder->CreateBlock(#block_name); - -void buildIsSoftMemoryLimitReached(JitTvmCodeGenContext* ctx) -{ - JIT_IF_BEGIN(soft_limit_reached) - JIT_IF_EVAL_CMP(AddIsSoftMemoryLimitReached(ctx), JIT_CONST(0), JIT_ICMP_NE) - IssueDebugLog("Soft memory limit reached"); - JIT_RETURN_CONST(MOT::RC_MEMORY_ALLOCATION_ERROR); - JIT_ELSE() - IssueDebugLog("Soft memory limit not reached"); - JIT_IF_END() -} - -static Instruction* buildExpression(JitTvmCodeGenContext* ctx, Expression* expr) -{ - Instruction* expr_value = ctx->_builder->addExpression(expr); - - // generate code for checking expression evaluation only if the expression is fallible - if (expr->canFail()) { - Instruction* expr_rc = ctx->_builder->addGetExpressionRC(); - - JIT_IF_BEGIN(check_expression_failed) - JIT_IF_EVAL_CMP(expr_rc, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Expression execution failed"); - JIT_RETURN(expr_rc); - JIT_IF_END() - - IssueDebugLog("Expression execution succeeded"); - } - - return expr_value; -} - -static void buildWriteDatumColumn(JitTvmCodeGenContext* ctx, Instruction* row, int colid, Instruction* datum_value) -{ - // ATTENTION: The datum_value expression-instruction MUST be already evaluated before this code is executed - // That is the reason why the expression-instruction was added to the current block and now we - // use only a register-ref instruction - Instruction* set_null_bit_res = AddSetExprResultNullBit(ctx, row, colid); - IssueDebugLog("Set null bit"); - - if (ctx->_table_info.m_table->GetField(colid)->m_isNotNull) { - JIT_IF_BEGIN(check_null_violation) - JIT_IF_EVAL_CMP(set_null_bit_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Null constraint violated"); - JIT_RETURN(set_null_bit_res); - JIT_IF_END() - } - - // now check if the result is not null, and if so write column datum - Instruction* is_expr_null = AddGetExprArgIsNull(ctx, 0); - JIT_IF_BEGIN(check_expr_null) - JIT_IF_EVAL_NOT(is_expr_null) - IssueDebugLog("Encountered non-null expression result, writing datum column"); - AddWriteDatumColumn(ctx, colid, row, datum_value); - JIT_IF_END() -} - -void buildWriteRow(JitTvmCodeGenContext* ctx, Instruction* row, bool isPKey, JitTvmRuntimeCursor* cursor) -{ - IssueDebugLog("Writing row"); - Instruction* write_row_res = AddWriteRow(ctx, row); - - JIT_IF_BEGIN(check_row_written) - JIT_IF_EVAL_CMP(write_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not written"); - // need to emit cleanup code - if (!isPKey) { - AddDestroyCursor(ctx, cursor); - } - JIT_RETURN(write_row_res); - JIT_IF_END() -} - -void buildResetRowsProcessed(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) ResetRowsProcessedInstruction()); -} - -void buildIncrementRowsProcessed(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) IncrementRowsProcessedInstruction()); -} - -Instruction* buildCreateNewRow(JitTvmCodeGenContext* ctx) -{ - Instruction* row = AddCreateNewRow(ctx); - - JIT_IF_BEGIN(check_row_created) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Failed to create row"); - JIT_RETURN_CONST(MOT::RC_MEMORY_ALLOCATION_ERROR); - JIT_IF_END() - - return row; -} - -Instruction* buildSearchRow(JitTvmCodeGenContext* ctx, MOT::AccessType access_type, JitRangeScanType range_scan_type, - int subQueryIndex /* = -1 */) -{ - IssueDebugLog("Searching row"); - Instruction* row = AddSearchRow(ctx, access_type, range_scan_type, subQueryIndex); - - JIT_IF_BEGIN(check_row_found) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Row not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() - - IssueDebugLog("Row found"); - return row; -} - -static Expression* buildFilter(JitTvmCodeGenContext* ctx, Instruction* row, JitFilter* filter, int* max_arg) -{ - Expression* result = nullptr; - Expression* lhs_expr = ProcessExpr(ctx, row, filter->_lhs_operand, max_arg); - if (lhs_expr == nullptr) { - MOT_LOG_TRACE( - "buildFilter(): Failed to process LHS expression with type %d", (int)filter->_lhs_operand->_expr_type); - } else { - Expression* rhs_expr = ProcessExpr(ctx, row, filter->_rhs_operand, max_arg); - if (rhs_expr == nullptr) { - MOT_LOG_TRACE( - "buildFilter(): Failed to process RHS expression with type %d", (int)filter->_rhs_operand->_expr_type); - delete lhs_expr; - } else { - result = ProcessFilterExpr(ctx, row, filter, max_arg); - } - } - return result; -} - -bool buildFilterRow( - JitTvmCodeGenContext* ctx, Instruction* row, JitFilterArray* filters, int* max_arg, BasicBlock* next_block) -{ - // 1. for each filter expression we generate the equivalent instructions which should evaluate to true or false - // 2. We assume that all filters are applied with AND operator between them (imposed during query analysis/plan - // phase) - for (int i = 0; i < filters->_filter_count; ++i) { - Expression* filter_expr = buildFilter(ctx, row, &filters->_scan_filters[i], max_arg); - if (filter_expr == nullptr) { - MOT_LOG_TRACE("buildFilterRow(): Failed to process filter expression %d", i); - return false; - } - - JIT_IF_BEGIN(filter_expr) - IssueDebugLog("Checking of row passes filter"); - Instruction* value = buildExpression(ctx, filter_expr); // this is a boolean datum result - JIT_IF_EVAL_NOT(value) - IssueDebugLog("Row did not pass filter"); - if (next_block == nullptr) { - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - } else { - ctx->_builder->CreateBr(next_block); - } - JIT_ELSE() - IssueDebugLog("Row passed filter"); - JIT_IF_END() - } - return true; -} - -void buildInsertRow(JitTvmCodeGenContext* ctx, Instruction* row) -{ - IssueDebugLog("Inserting row"); - Instruction* insert_row_res = AddInsertRow(ctx, row); - - JIT_IF_BEGIN(check_row_inserted) - JIT_IF_EVAL_CMP(insert_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not inserted"); - JIT_RETURN(insert_row_res); - JIT_IF_END() - - IssueDebugLog("Row inserted"); -} - -void buildDeleteRow(JitTvmCodeGenContext* ctx) -{ - IssueDebugLog("Deleting row"); - Instruction* delete_row_res = AddDeleteRow(ctx); - - JIT_IF_BEGIN(check_delete_row) - JIT_IF_EVAL_CMP(delete_row_res, JIT_CONST(MOT::RC_OK), JIT_ICMP_NE) - IssueDebugLog("Row not deleted"); - JIT_RETURN(delete_row_res); - JIT_IF_END() - - IssueDebugLog("Row deleted"); -} - -static Instruction* buildSearchIterator(JitTvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitRangeBoundMode range_bound_mode, JitRangeScanType range_scan_type, int subQueryIndex = -1) -{ - // search the row - IssueDebugLog("Searching range start"); - Instruction* itr = AddSearchIterator(ctx, index_scan_direction, range_bound_mode, range_scan_type, subQueryIndex); - - JIT_IF_BEGIN(check_itr_found) - JIT_IF_EVAL_NOT(itr) - IssueDebugLog("Range start not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() - - IssueDebugLog("Range start found"); - return itr; -} - -/** @brief Adds code to search for an iterator. */ -static Instruction* buildBeginIterator( - JitTvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex = -1) -{ - // search the row - IssueDebugLog("Getting begin iterator for full-scan"); - Instruction* itr = AddBeginIterator(ctx, rangeScanType, subQueryIndex); - - JIT_IF_BEGIN(check_itr_found) - JIT_IF_EVAL_NOT(itr) - IssueDebugLog("Begin iterator not found"); - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - JIT_IF_END() - - IssueDebugLog("Range start found"); - return itr; -} - -Instruction* buildGetRowFromIterator(JitTvmCodeGenContext* ctx, BasicBlock* endLoopBlock, MOT::AccessType access_mode, - JitIndexScanDirection index_scan_direction, JitTvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, - int subQueryIndex /* = -1 */) -{ - IssueDebugLog("Retrieving row from iterator"); - Instruction* row = - AddGetRowFromIterator(ctx, access_mode, index_scan_direction, cursor, range_scan_type, subQueryIndex); - - JIT_IF_BEGIN(check_itr_row_found) - JIT_IF_EVAL_NOT(row) - IssueDebugLog("Iterator row not found"); - JIT_GOTO(endLoopBlock); - // NOTE: we can actually do here a JIT_WHILE_BREAK() if we have an enclosing while loop - JIT_IF_END() - - IssueDebugLog("Iterator row found"); - return row; -} - -static Expression* ProcessConstExpr( - JitTvmCodeGenContext* ctx, const Const* const_value, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing CONST expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (IsTypeSupported(const_value->consttype)) { - result_type = const_value->consttype; - if (IsPrimitiveType(result_type)) { - result = - new (std::nothrow) ConstExpression(const_value->constvalue, arg_pos, (int)(const_value->constisnull)); - } else { - int constId = AllocateConstId(ctx, result_type, const_value->constvalue, const_value->constisnull); - if (constId == -1) { - MOT_LOG_TRACE("Failed to allocate constant identifier"); - } else { - result = AddGetConstAt(ctx, constId, arg_pos); - } - } - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } - } else { - MOT_LOG_TRACE("Failed to process const expression: type %d unsupported", (int)const_value->consttype); - } - - MOT_LOG_DEBUG("%*s <-- Processing CONST expression result: %p", depth, "", result); - return result; -} - -static Expression* ProcessParamExpr( - JitTvmCodeGenContext* ctx, const Param* param, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing PARAM expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (IsTypeSupported(param->paramtype)) { - result_type = param->paramtype; - result = AddGetDatumParam(ctx, param->paramid - 1, arg_pos); - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } - } else { - MOT_LOG_TRACE("Failed to process param expression: type %d unsupported", (int)param->paramtype); - } - - MOT_LOG_DEBUG("%*s <-- Processing PARAM expression result: %p", depth, "", result); - return result; -} - -static Expression* ProcessRelabelExpr( - JitTvmCodeGenContext* ctx, RelabelType* relabel_type, int& result_type, int arg_pos, int depth, int* max_arg) -{ - MOT_LOG_DEBUG("Processing RELABEL expression"); - Param* param = (Param*)relabel_type->arg; - return ProcessParamExpr(ctx, param, result_type, arg_pos, depth, max_arg); -} - -static Expression* ProcessVarExpr( - JitTvmCodeGenContext* ctx, const Var* var, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing VAR expression", depth, ""); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (IsTypeSupported(var->vartype)) { - result_type = var->vartype; - int table_colid = var->varattno; - result = AddReadDatumColumn(ctx, ctx->_table_info.m_table, nullptr, table_colid, arg_pos); - if (max_arg && (arg_pos > *max_arg)) { - *max_arg = arg_pos; - } - } else { - MOT_LOG_TRACE("Failed to process var expression: type %d unsupported", (int)var->vartype); - } - - MOT_LOG_DEBUG("%*s <-- Processing VAR expression result: %p", depth, "", result); - return result; -} - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0]); \ - break; - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1]); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], args[2]); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], arg_pos); \ - break; - -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], arg_pos); \ - break; - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], args[2], arg_pos); \ - break; - -static Expression* ProcessOpExpr( - JitTvmCodeGenContext* ctx, const OpExpr* op_expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - const int op_args_num = 3; - MOT_LOG_DEBUG("%*s --> Processing OP %u expression", depth, "", op_expr->opfuncid); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (list_length(op_expr->args) > op_args_num) { - MOT_LOG_TRACE("Unsupported operator %d: too many arguments", op_expr->opno); - return nullptr; - } - - Expression* args[op_args_num] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - int dummy = 0; - - ListCell* lc = nullptr; - foreach (lc, op_expr->args) { - Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = ProcessExpr(ctx, sub_expr, dummy, arg_pos + arg_num, depth + 1, max_arg); - if (args[arg_num] == nullptr) { - MOT_LOG_TRACE("Failed to process operator sub-expression %d", arg_num); - return nullptr; - } - if (++arg_num == op_args_num) { - break; - } - } - - result_type = op_expr->opresulttype; - switch (op_expr->opfuncid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported operator function type: %u", op_expr->opfuncid); - break; - } - - MOT_LOG_DEBUG("%*s <-- Processing OP %u expression result: %p", depth, "", op_expr->opfuncid, result); - return result; -} - -static Expression* ProcessFuncExpr( - JitTvmCodeGenContext* ctx, const FuncExpr* func_expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - const int func_args_num = 3; - MOT_LOG_DEBUG("%*s --> Processing FUNC %d expression", depth, "", (int)func_expr->funcid); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - if (list_length(func_expr->args) > func_args_num) { - MOT_LOG_TRACE("Unsupported function %d: too many arguments", func_expr->funcid); - return nullptr; - } - - Expression* args[func_args_num] = {nullptr, nullptr, nullptr}; - int arg_num = 0; - int dummy = 0; - - ListCell* lc = nullptr; - foreach (lc, func_expr->args) { - Expr* sub_expr = (Expr*)lfirst(lc); - args[arg_num] = ProcessExpr(ctx, sub_expr, dummy, arg_pos + arg_num, depth + 1, max_arg); - if (args[arg_num] == nullptr) { - MOT_LOG_TRACE("Failed to process function sub-expression %d", arg_num); - return nullptr; - } - if (++arg_num == func_args_num) { - break; - } - } - - result_type = func_expr->funcresulttype; - switch (func_expr->funcid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported function type: %d", (int)func_expr->funcid); - break; - } - - MOT_LOG_DEBUG("%*s <-- Processing FUNC %d expression result: %p", depth, "", (int)func_expr->funcid, result); - return result; -} - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -static Expression* ProcessExpr( - JitTvmCodeGenContext* ctx, Expr* expr, int& result_type, int arg_pos, int depth, int* max_arg) -{ - Expression* result = nullptr; - MOT_LOG_DEBUG("%*s --> Processing expression %d", depth, "", (int)expr->type); - if (depth > MOT_JIT_MAX_EXPR_DEPTH) { - MOT_LOG_TRACE("Cannot process expression: Expression exceeds depth limit %d", (int)MOT_JIT_MAX_EXPR_DEPTH); - return nullptr; - } - - // case 1: assign from parameter (cases like: s_quantity = $1) - if (expr->type == T_Const) { - result = ProcessConstExpr(ctx, (Const*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_Param) { - result = ProcessParamExpr(ctx, (Param*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_RelabelType) { - result = ProcessRelabelExpr(ctx, (RelabelType*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_Var) { - result = ProcessVarExpr(ctx, (Var*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_OpExpr) { - result = ProcessOpExpr(ctx, (OpExpr*)expr, result_type, arg_pos, depth + 1, max_arg); - } else if (expr->type == T_FuncExpr) { - result = ProcessFuncExpr(ctx, (FuncExpr*)expr, result_type, arg_pos, depth + 1, max_arg); - } else { - MOT_LOG_TRACE( - "Failed to generate jitted code for query: unsupported target expression type: %d", (int)expr->type); - } - - MOT_LOG_DEBUG("%*s <-- Processing expression %d result: %p", depth, "", (int)expr->type, result); - return result; -} - -static Expression* ProcessConstExpr(JitTvmCodeGenContext* ctx, const JitConstExpr* expr, int* max_arg) -{ - Expression* result = nullptr; - AddSetExprArgIsNull(ctx, expr->_arg_pos, expr->_is_null); // mark expression null status - if (IsPrimitiveType(expr->_const_type)) { - result = new (std::nothrow) ConstExpression(expr->_value, expr->_arg_pos, (int)(expr->_is_null)); - } else { - int constId = AllocateConstId(ctx, expr->_const_type, expr->_value, expr->_is_null); - if (constId == -1) { - MOT_LOG_TRACE("Failed to allocate constant identifier"); - } else { - result = AddGetConstAt(ctx, constId, expr->_arg_pos); - } - } - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } - return result; -} - -static Expression* ProcessParamExpr(JitTvmCodeGenContext* ctx, const JitParamExpr* expr, int* max_arg) -{ - Expression* result = AddGetDatumParam(ctx, expr->_param_id, expr->_arg_pos); - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } - return result; -} - -static Expression* ProcessVarExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitVarExpr* expr, int* max_arg) -{ - Expression* result = nullptr; - if (row == nullptr) { - MOT_LOG_TRACE("ProcessVarExpr(): Unexpected VAR expression without a row"); - } else { - result = AddReadDatumColumn(ctx, expr->_table, row, expr->_column_id, expr->_arg_pos); - if (max_arg && (expr->_arg_pos > *max_arg)) { - *max_arg = expr->_arg_pos; - } - } - return result; -} - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0]); \ - break; - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1]); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], args[2]); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], arg_pos); \ - break; - -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], arg_pos); \ - break; - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_DEBUG("Adding call to builtin: " #name); \ - result = new (std::nothrow) name##Operator(args[0], args[1], args[2], arg_pos); \ - break; - -static Expression* ProcessOpExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitOpExpr* expr, int* max_arg) -{ - Expression* result = nullptr; - - Expression* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; - - for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], max_arg); - if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process operator sub-expression %d", i); - for (int j = 0; j < i; ++j) { - delete args[j]; // cleanup after error - } - return nullptr; - } - } - - int arg_pos = expr->_arg_pos; - switch (expr->_op_func_id) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported operator function type: %d", expr->_op_func_id); - break; - } - - return result; -} - -static Expression* ProcessFuncExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitFuncExpr* expr, int* max_arg) -{ - Expression* result = nullptr; - - Expression* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; - - for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], max_arg); - if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process function sub-expression %d", i); - for (int j = 0; j < i; ++j) { - delete args[j]; // cleanup after error - } - return nullptr; - } - } - - int arg_pos = expr->_arg_pos; - switch (expr->_func_id) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported function type: %d", expr->_func_id); - break; - } - - return result; -} - -// we allow only binary operators for filters -#undef APPLY_UNARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to unary builtin: " #name); \ - break; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to ternary builtin: " #name); \ - break; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to unary cast builtin: " #name); \ - break; - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - case funcid: \ - MOT_LOG_TRACE("Unexpected call in filter expression to ternary cast builtin: " #name); \ - break; - -static Expression* ProcessFilterExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitFilter* filter, int* max_arg) -{ - Expression* result = nullptr; - - Expression* args[MOT_JIT_MAX_FUNC_EXPR_ARGS] = {nullptr, nullptr, nullptr}; - - args[0] = ProcessExpr(ctx, row, filter->_lhs_operand, max_arg); - if (!args[0]) { - MOT_LOG_TRACE("Failed to process filter LHS expression"); - return nullptr; - } - - args[1] = ProcessExpr(ctx, row, filter->_rhs_operand, max_arg); - if (!args[1]) { - MOT_LOG_TRACE("Failed to process filter RHS expression"); - delete args[0]; - return nullptr; - } - - int arg_pos = 0; // always a top-level expression - switch (filter->_filter_op_funcid) { - APPLY_OPERATORS() - - default: - MOT_LOG_TRACE("Unsupported filter function type: %d", filter->_filter_op_funcid); - break; - } - - return result; -} - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -static Expression* ProcessSubLinkExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitSubLinkExpr* expr, int* max_arg) -{ - return AddSelectSubQueryResult(ctx, expr->_sub_query_index); -} - -static Expression* ProcessBoolExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitBoolExpr* expr, int* maxArg) -{ - Expression* result = nullptr; - - Expression* args[MOT_JIT_MAX_BOOL_EXPR_ARGS] = {nullptr, nullptr}; - int argNum = 0; - - for (int i = 0; i < expr->_arg_count; ++i) { - args[i] = ProcessExpr(ctx, row, expr->_args[i], maxArg); - if (args[i] == nullptr) { - MOT_LOG_TRACE("Failed to process boolean sub-expression %d", argNum); - return nullptr; - } - } - - switch (expr->_bool_expr_type) { - case NOT_EXPR: - result = new (std::nothrow) NotExpression(args[0]); - break; - - case AND_EXPR: - result = new (std::nothrow) AndExpression(args[0], args[1]); - break; - - case OR_EXPR: - result = new (std::nothrow) OrExpression(args[0], args[1]); - break; - - default: - MOT_LOG_TRACE("Unsupported boolean expression type: %d", (int)expr->_bool_expr_type); - break; - } - - return result; -} - -static Expression* ProcessExpr(JitTvmCodeGenContext* ctx, Instruction* row, JitExpr* expr, int* max_arg) -{ - Expression* result = nullptr; - - if (expr->_expr_type == JIT_EXPR_TYPE_CONST) { - result = ProcessConstExpr(ctx, (JitConstExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_PARAM) { - result = ProcessParamExpr(ctx, (JitParamExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_VAR) { - result = ProcessVarExpr(ctx, row, (JitVarExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_OP) { - result = ProcessOpExpr(ctx, row, (JitOpExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_FUNC) { - result = ProcessFuncExpr(ctx, row, (JitFuncExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_SUBLINK) { - result = ProcessSubLinkExpr(ctx, row, (JitSubLinkExpr*)expr, max_arg); - } else if (expr->_expr_type == JIT_EXPR_TYPE_BOOL) { - result = ProcessBoolExpr(ctx, row, (JitBoolExpr*)expr, max_arg); - } else { - MOT_LOG_TRACE( - "Failed to generate jitted code for query: unsupported target expression type: %d", (int)expr->_expr_type); - } - - return result; -} - -static bool buildScanExpression(JitTvmCodeGenContext* ctx, JitColumnExpr* expr, int* max_arg, - JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, Instruction* outer_row, int subQueryIndex) -{ - Expression* value_expr = ProcessExpr(ctx, outer_row, expr->_expr, max_arg); - if (value_expr == nullptr) { - MOT_LOG_TRACE("buildScanExpression(): Failed to process expression with type %d", (int)expr->_expr->_expr_type); - return false; - } else { - Instruction* value = buildExpression(ctx, value_expr); - Instruction* column = AddGetColumnAt(ctx, - expr->_table_column_id, - range_scan_type, // no need to translate to zero-based index (first column is null bits) - subQueryIndex); - int index_colid = -1; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - index_colid = ctx->m_innerTable_info.m_columnMap[expr->_table_column_id]; - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - index_colid = ctx->_table_info.m_columnMap[expr->_table_column_id]; - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - index_colid = ctx->m_subQueryTableInfo[subQueryIndex].m_columnMap[expr->_table_column_id]; - } - AddBuildDatumKey( - ctx, column, index_colid, value, expr->_column_type, range_itr_type, range_scan_type, subQueryIndex); - } - return true; -} - -bool buildPointScan(JitTvmCodeGenContext* ctx, JitColumnExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, Instruction* outer_row, int expr_count /* = -1 */, int subQueryIndex /* = -1 */) -{ - if (expr_count == -1) { - expr_count = expr_array->_count; - } - AddInitSearchKey(ctx, range_scan_type, subQueryIndex); - for (int i = 0; i < expr_count; ++i) { - JitColumnExpr* expr = &expr_array->_exprs[i]; - - // validate the expression refers to the right table (in search expressions array, all expressions refer to the - // same table) - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - if (expr->_table != ctx->m_innerTable_info.m_table) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate TVM JIT Code", - "Invalid expression table (expected inner table %s, got %s)", - ctx->m_innerTable_info.m_table->GetTableName().c_str(), - expr->_table->GetTableName().c_str()); - return false; - } - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (expr->_table != ctx->_table_info.m_table) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate TVM JIT Code", - "Invalid expression table (expected main/outer table %s, got %s)", - ctx->_table_info.m_table->GetTableName().c_str(), - expr->_table->GetTableName().c_str()); - return false; - } - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (expr->_table != ctx->m_subQueryTableInfo[subQueryIndex].m_table) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate TVM JIT Code", - "Invalid expression table (expected sub-query table %s, got %s)", - ctx->m_subQueryTableInfo[subQueryIndex].m_table->GetTableName().c_str(), - expr->_table->GetTableName().c_str()); - return false; - } - } - - // prepare the sub-expression - if (!buildScanExpression( - ctx, expr, max_arg, JIT_RANGE_ITERATOR_START, range_scan_type, outer_row, subQueryIndex)) { - return false; - } - } - return true; -} - -bool writeRowColumns( - JitTvmCodeGenContext* ctx, Instruction* row, JitColumnExprArray* expr_array, int* max_arg, bool is_update) -{ - for (int i = 0; i < expr_array->_count; ++i) { - JitColumnExpr* column_expr = &expr_array->_exprs[i]; - - Expression* expr = ProcessExpr(ctx, row, column_expr->_expr, max_arg); - if (expr == nullptr) { - MOT_LOG_TRACE("ProcessExpr() returned NULL"); - return false; - } - - // set null bit or copy result data to column - Instruction* value = buildExpression(ctx, expr); - buildWriteDatumColumn(ctx, row, column_expr->_table_column_id, value); - - // set bit for incremental redo - if (is_update) { - AddSetBit(ctx, column_expr->_table_column_id - 1); - } - } - - return true; -} - -bool selectRowColumns(JitTvmCodeGenContext* ctx, Instruction* row, JitSelectExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, int subQueryIndex /* = -1 */) -{ - for (int i = 0; i < expr_array->_count; ++i) { - JitSelectExpr* expr = &expr_array->_exprs[i]; - // we skip expressions that select from other tables - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - if (expr->_column_expr->_table != ctx->m_innerTable_info.m_table) { - continue; - } - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (expr->_column_expr->_table != ctx->_table_info.m_table) { - continue; - } - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (expr->_column_expr->_table != ctx->m_subQueryTableInfo[subQueryIndex].m_table) { - continue; - } - } - AddSelectColumn( - ctx, row, expr->_column_expr->_column_id, expr->_tuple_column_id, range_scan_type, subQueryIndex); - } - - return true; -} - -static bool buildClosedRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, Instruction* outerRow, int subQueryIndex) -{ - // a closed range scan starts just like a point scan (with not enough search expressions) and then adds key patterns - bool result = buildPointScan(ctx, &indexScan->_search_exprs, maxArg, rangeScanType, outerRow, -1, subQueryIndex); - if (result) { - AddCopyKey(ctx, rangeScanType, subQueryIndex); - - // now fill each key with the right pattern for the missing pkey columns in the where clause - bool ascending = (indexScan->_sort_order == JIT_QUERY_SORT_ASCENDING); - - int* index_column_offsets = nullptr; - const uint16_t* key_length = nullptr; - int index_column_count = 0; - if (rangeScanType == JIT_RANGE_SCAN_INNER) { - index_column_offsets = ctx->m_innerTable_info.m_indexColumnOffsets; - key_length = ctx->m_innerTable_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->m_innerTable_info.m_index->GetNumFields(); - } else if (rangeScanType == JIT_RANGE_SCAN_MAIN) { - index_column_offsets = ctx->_table_info.m_indexColumnOffsets; - key_length = ctx->_table_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->_table_info.m_index->GetNumFields(); - } else if (rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - index_column_offsets = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets; - key_length = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields(); - index_column_count = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetNumFields(); - } - - int first_zero_column = indexScan->_column_count; - for (int i = first_zero_column; i < index_column_count; ++i) { - int offset = index_column_offsets[i]; - int size = key_length[i]; - MOT_LOG_DEBUG( - "Filling begin/end iterator pattern for missing pkey fields at offset %d, size %d", offset, size); - AddFillKeyPattern( - ctx, ascending ? 0x00 : 0xFF, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - AddFillKeyPattern( - ctx, ascending ? 0xFF : 0x00, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); - } - - AddAdjustKey(ctx, - ascending ? 0x00 : 0xFF, - JIT_RANGE_ITERATOR_START, - rangeScanType, // currently this is relevant only for secondary index searches - subQueryIndex); - AddAdjustKey(ctx, - ascending ? 0xFF : 0x00, - JIT_RANGE_ITERATOR_END, - rangeScanType, // currently this is relevant only for secondary index searches - subQueryIndex); - } - return result; -} - -static void BuildAscendingSemiOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - Instruction* outerRow, int subQueryIndex, int offset, int size, JitColumnExpr* lastExpr) -{ - if ((indexScan->_last_dim_op1 == JIT_WOC_LESS_THAN) || (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS)) { - // this is an upper bound operator on an ascending semi-open scan so we fill the begin key with zeros, - // and the end key with the value - AddFillKeyPattern(ctx, 0x00, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); - *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; - *endRangeBound = - (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } else { - // this is a lower bound operator on an ascending semi-open scan so we fill the begin key with the - // value, and the end key with 0xFF - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); - AddFillKeyPattern(ctx, 0xFF, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); - *beginRangeBound = - (indexScan->_last_dim_op1 == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = JIT_RANGE_BOUND_INCLUDE; - } -} - -static void BuildDescendingSemiOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - Instruction* outerRow, int subQueryIndex, int offset, int size, JitColumnExpr* lastExpr) -{ - if ((indexScan->_last_dim_op1 == JIT_WOC_LESS_THAN) || (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS)) { - // this is an upper bound operator on a descending semi-open scan so we fill the begin key with value, - // and the end key with zeroes - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_START, rangeScanType, outerRow, subQueryIndex); - AddFillKeyPattern(ctx, 0x00, offset, size, JIT_RANGE_ITERATOR_END, rangeScanType, subQueryIndex); - *beginRangeBound = - (indexScan->_last_dim_op1 == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = JIT_RANGE_BOUND_INCLUDE; - } else { - // this is a lower bound operator on a descending semi-open scan so we fill the begin key with 0xFF, and - // the end key with the value - AddFillKeyPattern(ctx, 0xFF, offset, size, JIT_RANGE_ITERATOR_START, rangeScanType, subQueryIndex); - buildScanExpression(ctx, lastExpr, maxArg, JIT_RANGE_ITERATOR_END, rangeScanType, outerRow, subQueryIndex); - *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; - *endRangeBound = - (indexScan->_last_dim_op1 == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } -} - -static bool buildSemiOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, JitRangeBoundMode* begin_range_bound, JitRangeBoundMode* end_range_bound, - Instruction* outer_row, int subQueryIndex) -{ - // an open range scan starts just like a point scan (with not enough search expressions) and then adds key patterns - // we do not use the last search expression - bool result = buildPointScan(ctx, - &index_scan->_search_exprs, - max_arg, - range_scan_type, - outer_row, - index_scan->_search_exprs._count - 1, - subQueryIndex); - if (result) { - AddCopyKey(ctx, range_scan_type, subQueryIndex); - - // now fill each key with the right pattern for the missing pkey columns in the where clause - bool ascending = (index_scan->_sort_order == JIT_QUERY_SORT_ASCENDING); - - int* index_column_offsets = nullptr; - const uint16_t* key_length = nullptr; - int index_column_count = 0; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - index_column_offsets = ctx->m_innerTable_info.m_indexColumnOffsets; - key_length = ctx->m_innerTable_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->m_innerTable_info.m_index->GetNumFields(); - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - index_column_offsets = ctx->_table_info.m_indexColumnOffsets; - key_length = ctx->_table_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->_table_info.m_index->GetNumFields(); - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - index_column_offsets = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets; - key_length = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields(); - index_column_count = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetNumFields(); - } - - // prepare offset and size for last column in search - int last_dim_column = index_scan->_column_count - 1; - int offset = index_column_offsets[last_dim_column]; - int size = key_length[last_dim_column]; - - // now we fill the last dimension (override extra work of point scan above) - int last_expr_index = index_scan->_search_exprs._count - 1; - JitColumnExpr* last_expr = &index_scan->_search_exprs._exprs[last_expr_index]; - if (ascending) { - BuildAscendingSemiOpenRangeScan(ctx, - index_scan, - max_arg, - range_scan_type, - begin_range_bound, - end_range_bound, - outer_row, - subQueryIndex, - offset, - size, - last_expr); - } else { - BuildDescendingSemiOpenRangeScan(ctx, - index_scan, - max_arg, - range_scan_type, - begin_range_bound, - end_range_bound, - outer_row, - subQueryIndex, - offset, - size, - last_expr); - } - - // now fill the rest as usual - int first_zero_column = index_scan->_column_count; - for (int i = first_zero_column; i < index_column_count; ++i) { - int col_offset = index_column_offsets[i]; - int key_len = key_length[i]; - MOT_LOG_DEBUG("Filling begin/end iterator pattern for missing pkey fields at offset %d, size %d", - col_offset, - key_len); - AddFillKeyPattern(ctx, - ascending ? 0x00 : 0xFF, - col_offset, - key_len, - JIT_RANGE_ITERATOR_START, - range_scan_type, - subQueryIndex); - AddFillKeyPattern(ctx, - ascending ? 0xFF : 0x00, - col_offset, - key_len, - JIT_RANGE_ITERATOR_END, - range_scan_type, - subQueryIndex); - } - - AddAdjustKey(ctx, - ascending ? 0x00 : 0xFF, - JIT_RANGE_ITERATOR_START, - range_scan_type, // currently this is relevant only for secondary index searches - subQueryIndex); - AddAdjustKey(ctx, - ascending ? 0xFF : 0x00, - JIT_RANGE_ITERATOR_END, - range_scan_type, // currently this is relevant only for secondary index searches - subQueryIndex); - } - return result; -} - -static void BuildAscendingOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - Instruction* outerRow, int subQueryIndex, JitWhereOperatorClass beforeLastDimOp, JitWhereOperatorClass lastDimOp, - JitColumnExpr* beforeLastExpr, JitColumnExpr* lastExpr) -{ - if ((beforeLastDimOp == JIT_WOC_LESS_THAN) || (beforeLastDimOp == JIT_WOC_LESS_EQUALS)) { - MOT_ASSERT((lastDimOp == JIT_WOC_GREATER_THAN) || (lastDimOp == JIT_WOC_GREATER_EQUALS)); - // the before-last operator is an upper bound operator on an ascending open scan so we fill the begin - // key with the last value, and the end key with the before-last value - buildScanExpression(ctx, - lastExpr, - maxArg, - JIT_RANGE_ITERATOR_START, - rangeScanType, - outerRow, // lower bound on begin iterator key - subQueryIndex); - buildScanExpression(ctx, - beforeLastExpr, - maxArg, - JIT_RANGE_ITERATOR_END, - rangeScanType, - outerRow, // upper bound on end iterator key - subQueryIndex); - *beginRangeBound = (lastDimOp == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = (beforeLastDimOp == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } else { - MOT_ASSERT((lastDimOp == JIT_WOC_LESS_THAN) || (lastDimOp == JIT_WOC_LESS_EQUALS)); - // the before-last operator is a lower bound operator on an ascending open scan so we fill the begin key - // with the before-last value, and the end key with the last value - buildScanExpression(ctx, - beforeLastExpr, - maxArg, - JIT_RANGE_ITERATOR_START, - rangeScanType, - outerRow, // lower bound on begin iterator key - subQueryIndex); - buildScanExpression(ctx, - lastExpr, - maxArg, - JIT_RANGE_ITERATOR_END, - rangeScanType, - outerRow, // upper bound on end iterator key - subQueryIndex); - *beginRangeBound = - (beforeLastDimOp == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = (lastDimOp == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } -} - -static void BuildDescendingOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - Instruction* outerRow, int subQueryIndex, JitWhereOperatorClass beforeLastDimOp, JitWhereOperatorClass lastDimOp, - JitColumnExpr* beforeLastExpr, JitColumnExpr* lastExpr) -{ - if ((beforeLastDimOp == JIT_WOC_LESS_THAN) || (beforeLastDimOp == JIT_WOC_LESS_EQUALS)) { - MOT_ASSERT((lastDimOp == JIT_WOC_GREATER_THAN) || (lastDimOp == JIT_WOC_GREATER_EQUALS)); - // the before-last operator is an upper bound operator on an descending open scan so we fill the begin - // key with the last value, and the end key with the before-last value - buildScanExpression(ctx, - beforeLastExpr, - maxArg, - JIT_RANGE_ITERATOR_START, - rangeScanType, - outerRow, // upper bound on begin iterator key - subQueryIndex); - buildScanExpression(ctx, - lastExpr, - maxArg, - JIT_RANGE_ITERATOR_END, - rangeScanType, - outerRow, // lower bound on end iterator key - subQueryIndex); - *beginRangeBound = (beforeLastDimOp == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = (lastDimOp == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } else { - MOT_ASSERT((lastDimOp == JIT_WOC_LESS_THAN) || (lastDimOp == JIT_WOC_LESS_EQUALS)); - // the before-last operator is a lower bound operator on an descending open scan so we fill the begin - // key with the last value, and the end key with the before-last value - buildScanExpression(ctx, - lastExpr, - maxArg, - JIT_RANGE_ITERATOR_START, - rangeScanType, - outerRow, // upper bound on begin iterator key - subQueryIndex); - buildScanExpression(ctx, - beforeLastExpr, - maxArg, - JIT_RANGE_ITERATOR_END, - rangeScanType, - outerRow, // lower bound on end iterator key - subQueryIndex); - *beginRangeBound = (lastDimOp == JIT_WOC_LESS_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - *endRangeBound = - (beforeLastDimOp == JIT_WOC_GREATER_EQUALS) ? JIT_RANGE_BOUND_INCLUDE : JIT_RANGE_BOUND_EXCLUDE; - } -} - -static bool buildOpenRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, JitRangeBoundMode* begin_range_bound, JitRangeBoundMode* end_range_bound, - Instruction* outer_row, int subQueryIndex) -{ - // an open range scan starts just like a point scan (with not enough search expressions) and then adds key patterns - // we do not use the last two expressions - bool result = buildPointScan(ctx, - &index_scan->_search_exprs, - max_arg, - range_scan_type, - outer_row, - index_scan->_search_exprs._count - 2, - subQueryIndex); - if (result) { - AddCopyKey(ctx, range_scan_type, subQueryIndex); - - // now fill each key with the right pattern for the missing pkey columns in the where clause - bool ascending = (index_scan->_sort_order == JIT_QUERY_SORT_ASCENDING); - - int* index_column_offsets = nullptr; - const uint16_t* key_length = nullptr; - int index_column_count = 0; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - index_column_offsets = ctx->m_innerTable_info.m_indexColumnOffsets; - key_length = ctx->m_innerTable_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->m_innerTable_info.m_index->GetNumFields(); - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - index_column_offsets = ctx->_table_info.m_indexColumnOffsets; - key_length = ctx->_table_info.m_index->GetLengthKeyFields(); - index_column_count = ctx->_table_info.m_index->GetNumFields(); - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - index_column_offsets = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets; - key_length = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields(); - index_column_count = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetNumFields(); - } - - // now we fill the last dimension (override extra work of point scan above) - JitWhereOperatorClass before_last_dim_op = index_scan->_last_dim_op1; // avoid confusion, and give proper names - JitWhereOperatorClass last_dim_op = index_scan->_last_dim_op2; // avoid confusion, and give proper names - int last_expr_index = index_scan->_search_exprs._count - 1; - JitColumnExpr* last_expr = &index_scan->_search_exprs._exprs[last_expr_index]; - JitColumnExpr* before_last_expr = &index_scan->_search_exprs._exprs[last_expr_index - 1]; - if (ascending) { - BuildAscendingOpenRangeScan(ctx, - index_scan, - max_arg, - range_scan_type, - begin_range_bound, - end_range_bound, - outer_row, - subQueryIndex, - before_last_dim_op, - last_dim_op, - before_last_expr, - last_expr); - } else { - BuildDescendingOpenRangeScan(ctx, - index_scan, - max_arg, - range_scan_type, - begin_range_bound, - end_range_bound, - outer_row, - subQueryIndex, - before_last_dim_op, - last_dim_op, - before_last_expr, - last_expr); - } - - // now fill the rest as usual - int first_zero_column = index_scan->_column_count; - for (int i = first_zero_column; i < index_column_count; ++i) { - int offset = index_column_offsets[i]; - int size = key_length[i]; - MOT_LOG_DEBUG( - "Filling begin/end iterator pattern for missing pkey fields at offset %d, size %d", offset, size); - AddFillKeyPattern( - ctx, ascending ? 0x00 : 0xFF, offset, size, JIT_RANGE_ITERATOR_START, range_scan_type, subQueryIndex); - AddFillKeyPattern( - ctx, ascending ? 0xFF : 0x00, offset, size, JIT_RANGE_ITERATOR_END, range_scan_type, subQueryIndex); - } - - AddAdjustKey(ctx, - ascending ? 0x00 : 0xFF, - JIT_RANGE_ITERATOR_START, - range_scan_type, // currently this is relevant only for secondary index searches - subQueryIndex); - AddAdjustKey(ctx, - ascending ? 0xFF : 0x00, - JIT_RANGE_ITERATOR_END, - range_scan_type, // currently this is relevant only for secondary index searches - subQueryIndex); - } - return result; -} - -static bool buildRangeScan(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitRangeBoundMode* beginRangeBound, JitRangeBoundMode* endRangeBound, - Instruction* outerRow, int subQueryIndex = -1) -{ - bool result = false; - - // if this is a point scan we generate two identical keys for the iterators - if (indexScan->_scan_type == JIT_INDEX_SCAN_POINT) { - result = buildPointScan(ctx, &indexScan->_search_exprs, maxArg, rangeScanType, outerRow, -1, subQueryIndex); - if (result) { - AddCopyKey(ctx, rangeScanType, subQueryIndex); - *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; - *endRangeBound = JIT_RANGE_BOUND_INCLUDE; - } - } else if (indexScan->_scan_type == JIT_INDEX_SCAN_CLOSED) { - result = buildClosedRangeScan(ctx, indexScan, maxArg, rangeScanType, outerRow, subQueryIndex); - if (result) { - *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; - *endRangeBound = JIT_RANGE_BOUND_INCLUDE; - } - } else if (indexScan->_scan_type == JIT_INDEX_SCAN_SEMI_OPEN) { - result = buildSemiOpenRangeScan( - ctx, indexScan, maxArg, rangeScanType, beginRangeBound, endRangeBound, outerRow, subQueryIndex); - } else if (indexScan->_scan_type == JIT_INDEX_SCAN_OPEN) { - result = buildOpenRangeScan( - ctx, indexScan, maxArg, rangeScanType, beginRangeBound, endRangeBound, outerRow, subQueryIndex); - } else if (indexScan->_scan_type == JIT_INDEX_SCAN_FULL) { - result = true; // no keys used - *beginRangeBound = JIT_RANGE_BOUND_INCLUDE; - *endRangeBound = JIT_RANGE_BOUND_INCLUDE; - } - return result; -} - -static bool buildPrepareStateScan(JitTvmCodeGenContext* ctx, JitIndexScan* index_scan, int* max_arg, - JitRangeScanType range_scan_type, Instruction* outer_row) -{ - JitRangeBoundMode begin_range_bound = JIT_RANGE_BOUND_NONE; - JitRangeBoundMode end_range_bound = JIT_RANGE_BOUND_NONE; - - // emit code to check if state iterators are null - JIT_IF_BEGIN(state_iterators_exist) - Instruction* is_begin_itr_null = AddIsStateIteratorNull(ctx, JIT_RANGE_ITERATOR_START, range_scan_type); - JIT_IF_EVAL(is_begin_itr_null) - // prepare search keys - if (!buildRangeScan(ctx, index_scan, max_arg, range_scan_type, &begin_range_bound, &end_range_bound, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for range select query: unsupported WHERE clause type"); - return false; - } - - // search begin iterator and save it in execution state - IssueDebugLog("Building search iterator from search key, and saving in execution state"); - if (index_scan->_scan_type == JIT_INDEX_SCAN_FULL) { - Instruction* itr = buildBeginIterator(ctx, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); - } else { - Instruction* itr = buildSearchIterator(ctx, index_scan->_scan_direction, begin_range_bound, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_START, range_scan_type); - - // create end iterator and save it in execution state - IssueDebugLog("Creating end iterator from end search key, and saving in execution state"); - itr = AddCreateEndIterator(ctx, index_scan->_scan_direction, end_range_bound, range_scan_type); - AddSetStateIterator(ctx, itr, JIT_RANGE_ITERATOR_END, range_scan_type); - } - - // initialize state scan variables - if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - AddResetStateLimitCounter(ctx); // in case there is a limit clause - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // in case this is a join query - AddSetStateScanEndFlag( - ctx, 0, JIT_RANGE_SCAN_MAIN); // reset state flag (only once, and not repeatedly if row filter failed) - } - JIT_IF_END() - - return true; -} - -static bool buildPrepareStateRow(JitTvmCodeGenContext* ctx, MOT::AccessType access_mode, JitIndexScan* index_scan, - int* max_arg, JitRangeScanType range_scan_type, BasicBlock* next_block) -{ - MOT_LOG_DEBUG("Generating select code for stateful range select"); - Instruction* row = nullptr; - - // we start a new block so current block must end with terminator - DEFINE_BLOCK(prepare_state_row_bb, ctx->m_jittedQuery); - ctx->_builder->CreateBr(prepare_state_row_bb); - ctx->_builder->SetInsertPoint(prepare_state_row_bb); - - // check if state row is null - JIT_IF_BEGIN(test_state_row) - IssueDebugLog("Checking if state row is NULL"); - Instruction* res = AddIsStateRowNull(ctx, range_scan_type); - JIT_IF_EVAL(res) - IssueDebugLog("State row is NULL, fetching from state iterators"); - - // check if state scan ended - JIT_IF_BEGIN(test_scan) - IssueDebugLog("Checking if state scan ended"); - BasicBlock* isStateScanEndBlock = JIT_CURRENT_BLOCK(); // remember current block if filter fails - Instruction* res_scan_end = AddIsStateScanEnd(ctx, index_scan->_scan_direction, range_scan_type); - JIT_IF_EVAL(res_scan_end) - // fail scan block - IssueDebugLog("Scan ended, raising internal state scan end flag"); - AddSetStateScanEndFlag(ctx, 1, range_scan_type); - JIT_ELSE() - // now get row from iterator - IssueDebugLog("State scan not ended - Retrieving row from iterator"); - row = AddGetRowFromStateIterator(ctx, access_mode, index_scan->_scan_direction, range_scan_type); - - // check if row was found - JIT_IF_BEGIN(test_row_found) - JIT_IF_EVAL_NOT(row) - // row not found branch - IssueDebugLog("Could not retrieve row from state iterator, raising internal state scan end flag"); - AddSetStateScanEndFlag(ctx, 1, range_scan_type); - JIT_ELSE() - // row found, check for additional filters, if not passing filter then go back to execute getRowFromStateIterator() - if (!buildFilterRow(ctx, row, &index_scan->_filters, max_arg, isStateScanEndBlock)) { - MOT_LOG_TRACE("Failed to generate jitted code for query: failed to build filter expressions for row"); - return false; - } - // row passed all filters, so save it in state outer row - AddSetStateRow(ctx, row, range_scan_type); - if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - AddSetStateScanEndFlag(ctx, 0, JIT_RANGE_SCAN_INNER); // reset inner scan flag - } - JIT_IF_END() - JIT_IF_END() - JIT_IF_END() - - // cleanup state iterators if needed and return/jump to next block - JIT_IF_BEGIN(state_scan_ended) - IssueDebugLog("Checking if state scan ended flag was raised"); - Instruction* state_scan_end_flag = AddGetStateScanEndFlag(ctx, range_scan_type); - JIT_IF_EVAL(state_scan_end_flag) - IssueDebugLog(" State scan ended flag was raised, cleaning up iterators and reporting to user"); - AddDestroyStateIterators(ctx, range_scan_type); // cleanup - if ((range_scan_type == JIT_RANGE_SCAN_MAIN) || (next_block == nullptr)) { - // either a main scan ended (simple range or outer loop of join), - // or an inner range ended in an outer point join (so next block is null) - // in either case let caller know scan ended and return appripriate value - AddSetScanEnded(ctx, 1); // no outer row, we are definitely done - JIT_RETURN_CONST(MOT::RC_LOCAL_ROW_NOT_FOUND); - } else { - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); // make sure a new row is fetched in outer loop - ctx->_builder->CreateBr(next_block); // jump back to outer loop - } - JIT_IF_END() - - return true; -} - -Instruction* buildPrepareStateScanRow(JitTvmCodeGenContext* ctx, JitIndexScan* index_scan, - JitRangeScanType range_scan_type, MOT::AccessType access_mode, int* max_arg, Instruction* outer_row, - BasicBlock* next_block, BasicBlock** loop_block) -{ - // prepare stateful scan if not done so already - if (!buildPrepareStateScan(ctx, index_scan, max_arg, range_scan_type, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for range JOIN query: unsupported %s WHERE clause type", - (range_scan_type == JIT_RANGE_SCAN_MAIN) ? "outer" : "inner"); - return nullptr; - } - - // mark position for later jump - if (loop_block != nullptr) { - *loop_block = ctx->_builder->CreateBlock("fetch_outer_row_bb"); - ctx->_builder->CreateBr(*loop_block); // end current block - ctx->_builder->SetInsertPoint(*loop_block); // start new block - } - - // fetch row for read - if (!buildPrepareStateRow(ctx, access_mode, index_scan, max_arg, range_scan_type, next_block)) { - MOT_LOG_TRACE("Failed to generate jitted code for range JOIN query: failed to build search outer row block"); - return nullptr; - } - Instruction* row = AddGetStateRow(ctx, range_scan_type); - return row; -} - -JitTvmRuntimeCursor buildRangeCursor(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitIndexScanDirection indexScanDirection, Instruction* outerRow, - int subQueryIndex /* = -1 */) -{ - JitTvmRuntimeCursor result = {nullptr, nullptr}; - JitRangeBoundMode beginRangeBound = JIT_RANGE_BOUND_NONE; - JitRangeBoundMode endRangeBound = JIT_RANGE_BOUND_NONE; - if (!buildRangeScan( - ctx, indexScan, maxArg, rangeScanType, &beginRangeBound, &endRangeBound, outerRow, subQueryIndex)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported %s-loop WHERE clause type", - outerRow ? "inner" : "outer"); - return result; - } - - // build range iterators - result.begin_itr = buildSearchIterator(ctx, indexScanDirection, beginRangeBound, rangeScanType, subQueryIndex); - result.end_itr = - AddCreateEndIterator(ctx, indexScanDirection, endRangeBound, rangeScanType, subQueryIndex); // forward scan - - return result; -} - -static bool prepareAggregateAvg(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate) -{ - // although we already have this information in the aggregate descriptor, we still check again - switch (aggregate->_func_id) { - case INT8AVGFUNCOID: - case NUMERICAVGFUNCOID: - // the current_aggregate is a 2 numeric array - AddPrepareAvgArray(ctx, NUMERICOID, 2); - break; - - case INT4AVGFUNCOID: - case INT2AVGFUNCOID: - case 5537: // int1 avg - // the current_aggregate is a 2 int8 array - AddPrepareAvgArray(ctx, INT8OID, 2); - break; - - case 2104: // float4 - case 2105: // float8 - // the current_aggregate is a 3 float8 array - AddPrepareAvgArray(ctx, FLOAT8OID, 3); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate AVG() operator function type: %d", aggregate->_func_id); - return false; - } - - return true; -} - -static bool prepareAggregateSum(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate) -{ - switch (aggregate->_func_id) { - case INT8SUMFUNCOID: - // current sum in numeric, and value is int8, both can be null - AddResetAggValue(ctx, NUMERICOID); - break; - - case INT4SUMFUNCOID: - case INT2SUMFUNCOID: - // current aggregate is a int8, and value is int4, both can be null - AddResetAggValue(ctx, INT8OID); - break; - - case 2110: // float4 - // current aggregate is a float4, and value is float4, both can **NOT** be null - AddResetAggValue(ctx, FLOAT4OID); - break; - - case 2111: // float8 - // current aggregate is a float8, and value is float8, both can **NOT** be null - AddResetAggValue(ctx, FLOAT8OID); - break; - - case NUMERICSUMFUNCOID: - AddResetAggValue(ctx, NUMERICOID); - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate SUM() operator function type: %d", aggregate->_func_id); - return false; - } - return true; -} - -static bool prepareAggregateMaxMin(JitTvmCodeGenContext* ctx, JitAggregate* aggregate) -{ - AddResetAggMaxMinNull(ctx); - return true; -} - -static bool prepareAggregateCount(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate) -{ - switch (aggregate->_func_id) { - case 2147: // int8inc_any - case 2803: // int8inc - // current aggregate is int8, and can **NOT** be null - AddResetAggValue(ctx, INT8OID); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate COUNT() operator function type: %d", aggregate->_func_id); - return false; - } - return true; -} - -static bool prepareDistinctSet(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate) -{ - // we need a hash-set according to the aggregated type (preferably but not necessarily linear-probing hash) - // we use an opaque datum type, with a tailor-made hash-function and equals function - AddPrepareDistinctSet(ctx, aggregate->_element_type); - return true; -} - -bool prepareAggregate(JitTvmCodeGenContext* ctx, JitAggregate* aggregate) -{ - bool result = false; - - switch (aggregate->_aggreaget_op) { - case JIT_AGGREGATE_AVG: - result = prepareAggregateAvg(ctx, aggregate); - break; - - case JIT_AGGREGATE_SUM: - result = prepareAggregateSum(ctx, aggregate); - break; - - case JIT_AGGREGATE_MAX: - case JIT_AGGREGATE_MIN: - result = prepareAggregateMaxMin(ctx, aggregate); - break; - - case JIT_AGGREGATE_COUNT: - result = prepareAggregateCount(ctx, aggregate); - break; - - default: - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "JIT Compile", - "Cannot prepare for aggregation: invalid aggregate operator %d", - (int)aggregate->_aggreaget_op); - break; - } - - if (result) { - if (aggregate->_distinct) { - result = prepareDistinctSet(ctx, aggregate); - } - } - - return result; -} - -static Expression* buildAggregateAvg( - JitTvmCodeGenContext* ctx, const JitAggregate* aggregate, Expression* current_aggregate, Expression* var_expr) -{ - Expression* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8AVGFUNCOID: - // the current_aggregate is a 2 numeric array, and the var expression should evaluate to int8 - aggregate_expr = new (std::nothrow) int8_avg_accumOperator(current_aggregate, var_expr); - break; - - case INT4AVGFUNCOID: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int4 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, NULL)" return true - aggregate_expr = new (std::nothrow) int4_avg_accumOperator(current_aggregate, var_expr); - break; - - case INT2AVGFUNCOID: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int2 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, NULL)" return true - aggregate_expr = new (std::nothrow) int2_avg_accumOperator(current_aggregate, var_expr); - break; - - case 5537: - // the current_aggregate is a 2 int8 array, and the var expression should evaluate to int1 - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, NULL)" return true - aggregate_expr = new (std::nothrow) int1_avg_accumOperator(current_aggregate, var_expr); - break; - - case 2104: // float4 - // the current_aggregate is a 3 float8 array, and the var expression should evaluate to float4 - // the function computes 3 values: count, sum, square sum - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, NULL)" return true - aggregate_expr = new (std::nothrow) float4_accumOperator(current_aggregate, var_expr); - break; - - case 2105: // float8 - // the current_aggregate is a 3 float8 array, and the var expression should evaluate to float8 - // the function computes 3 values: count, sum, square sum - // we can save a palloc by ensuring "AggCheckCallContext(fcinfo, NULL)" return true - aggregate_expr = new (std::nothrow) float8_accumOperator(current_aggregate, var_expr); - break; - - case NUMERICAVGFUNCOID: - // the current_aggregate is a 2 numeric array, and the var expression should evaluate to numeric - aggregate_expr = new (std::nothrow) numeric_avg_accumOperator(current_aggregate, var_expr); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate AVG() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; -} - -static Expression* buildAggregateSum( - JitTvmCodeGenContext* ctx, const JitAggregate* aggregate, Expression* current_aggregate, Expression* var_expr) -{ - Expression* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8SUMFUNCOID: - // current sum in numeric, and next value is int8, both can be null - aggregate_expr = new (std::nothrow) int8_sumOperator(current_aggregate, var_expr); - break; - - case INT4SUMFUNCOID: - // current aggregate is a int8, and value is int4, both can be null - aggregate_expr = new (std::nothrow) int4_sumOperator(current_aggregate, var_expr); - break; - - case INT2SUMFUNCOID: - // current aggregate is a int8, and value is int3, both can be null - aggregate_expr = new (std::nothrow) int2_sumOperator(current_aggregate, var_expr); - break; - - case 2110: // float4 - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = new (std::nothrow) float4plOperator(current_aggregate, var_expr); - break; - - case 2111: // float8 - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = new (std::nothrow) float8plOperator(current_aggregate, var_expr); - break; - - case NUMERICSUMFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = new (std::nothrow) numeric_addOperator(current_aggregate, var_expr); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate SUM() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; -} - -static Expression* buildAggregateMax( - JitTvmCodeGenContext* ctx, const JitAggregate* aggregate, Expression* current_aggregate, Expression* var_expr) -{ - Expression* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8LARGERFUNCOID: - // current aggregate is a int8, and value is int8, both can **NOT** be null - aggregate_expr = new (std::nothrow) int8largerOperator(current_aggregate, var_expr); - break; - - case INT4LARGERFUNCOID: - // current aggregate is a int4, and value is int4, both can **NOT** be null - aggregate_expr = new (std::nothrow) int4largerOperator(current_aggregate, var_expr); - break; - - case INT2LARGERFUNCOID: - // current aggregate is a int2, and value is int2, both can **NOT** be null - aggregate_expr = new (std::nothrow) int2largerOperator(current_aggregate, var_expr); - break; - - case 5538: - // current aggregate is a int1, and value is int1, both can **NOT** be null - aggregate_expr = new (std::nothrow) int1largerOperator(current_aggregate, var_expr); - break; - - case 2119: // float4larger - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = new (std::nothrow) float4largerOperator(current_aggregate, var_expr); - break; - - case 2120: // float8larger - // current aggregate is a float8, and value is float8, both can **NOT** be null - aggregate_expr = new (std::nothrow) float8largerOperator(current_aggregate, var_expr); - break; - - case NUMERICLARGERFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = new (std::nothrow) numeric_largerOperator(current_aggregate, var_expr); - break; - - case 2126: - // current aggregate is a timestamp, and value is timestamp, both can **NOT** be null - aggregate_expr = new (std::nothrow) timestamp_largerOperator(current_aggregate, var_expr); - break; - - case 2122: - // current aggregate is a date, and value is date, both can **NOT** be null - aggregate_expr = new (std::nothrow) date_largerOperator(current_aggregate, var_expr); - break; - - case 2244: - // current aggregate is a bpchar, and value is bpchar, both can **NOT** be null - aggregate_expr = new (std::nothrow) bpchar_largerOperator(current_aggregate, var_expr); - break; - - case 2129: - // current aggregate is a text, and value is text, both can **NOT** be null - aggregate_expr = new (std::nothrow) text_largerOperator(current_aggregate, var_expr); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate MAX() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; -} - -static Expression* buildAggregateMin( - JitTvmCodeGenContext* ctx, const JitAggregate* aggregate, Expression* current_aggregate, Expression* var_expr) -{ - Expression* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case INT8SMALLERFUNCOID: - // current sum is a int8, and value is int8, both can **NOT** be null - aggregate_expr = new (std::nothrow) int8smallerOperator(current_aggregate, var_expr); - break; - - case INT4SMALLERFUNCOID: - // current aggregate is a int4, and value is int4, both can **NOT** be null - aggregate_expr = new (std::nothrow) int4smallerOperator(current_aggregate, var_expr); - break; - - case INT2SMALLERFUNCOID: - // current aggregate is a int2, and value is int2, both can **NOT** be null - aggregate_expr = new (std::nothrow) int2smallerOperator(current_aggregate, var_expr); - break; - - case 2135: // float4smaller - // current aggregate is a float4, and value is float4, both can **NOT** be null - aggregate_expr = new (std::nothrow) float4smallerOperator(current_aggregate, var_expr); - break; - - case 2120: // float8smaller - // current aggregate is a float8, and value is float8, both can **NOT** be null - aggregate_expr = new (std::nothrow) float8smallerOperator(current_aggregate, var_expr); - break; - - case NUMERICSMALLERFUNCOID: - // current aggregate is a numeric, and value is numeric, both can **NOT** be null - aggregate_expr = new (std::nothrow) numeric_smallerOperator(current_aggregate, var_expr); - break; - - case 2142: - // current aggregate is a timestamp, and value is timestamp, both can **NOT** be null - aggregate_expr = new (std::nothrow) timestamp_smallerOperator(current_aggregate, var_expr); - break; - - case 2138: - // current aggregate is a date, and value is date, both can **NOT** be null - aggregate_expr = new (std::nothrow) date_smallerOperator(current_aggregate, var_expr); - break; - - case 2245: - // current aggregate is a bpchar, and value is bpchar, both can **NOT** be null - aggregate_expr = new (std::nothrow) bpchar_smallerOperator(current_aggregate, var_expr); - break; - - case 2145: - // current aggregate is a text, and value is text, both can **NOT** be null - aggregate_expr = new (std::nothrow) text_smallerOperator(current_aggregate, var_expr); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate MIN() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; -} - -static Expression* buildAggregateCount( - JitTvmCodeGenContext* ctx, const JitAggregate* aggregate, Expression* count_aggregate) -{ - Expression* aggregate_expr = nullptr; - switch (aggregate->_func_id) { - case 2147: // int8inc_any - // current aggregate is int8, and can **NOT** be null - aggregate_expr = new (std::nothrow) int8incOperator(count_aggregate); - break; - - case 2803: // int8inc - // current aggregate is int8, and can **NOT** be null - aggregate_expr = new (std::nothrow) int8incOperator(count_aggregate); - break; - - default: - MOT_LOG_TRACE("Unsupported aggregate COUNT() operator function type: %d", aggregate->_func_id); - break; - } - - return aggregate_expr; -} - -static bool buildAggregateMaxMin(JitTvmCodeGenContext* ctx, JitAggregate* aggregate, Expression* var_expr) -{ - bool result = false; - Expression* aggregateExpr = nullptr; - - // we first check if the min/max value is null and if so just store the column value - JIT_IF_BEGIN(test_max_min_value_null) - Instruction* res = AddGetAggMaxMinIsNull(ctx); - JIT_IF_EVAL(res) - AddSetAggValue(ctx, var_expr); - AddSetAggMaxMinNotNull(ctx); - JIT_ELSE() - // get the aggregated value and call the operator - Expression* current_aggregate = AddGetAggValue(ctx); - switch (aggregate->_aggreaget_op) { - case JIT_AGGREGATE_MAX: - aggregateExpr = buildAggregateMax(ctx, aggregate, current_aggregate, var_expr); - break; - - case JIT_AGGREGATE_MIN: - aggregateExpr = buildAggregateMin(ctx, aggregate, current_aggregate, var_expr); - break; - - default: - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "JIT Compile", "Invalid aggregate operator %d", (int)aggregate->_aggreaget_op); - break; - } - JIT_IF_END() - - if (aggregateExpr != nullptr) { - AddSetAggValue(ctx, aggregateExpr); - result = true; - } - - return result; -} - -static bool buildAggregateTuple(JitTvmCodeGenContext* ctx, JitAggregate* aggregate, Expression* var_expr) -{ - bool result = false; - - if ((aggregate->_aggreaget_op == JIT_AGGREGATE_MAX) || (aggregate->_aggreaget_op == JIT_AGGREGATE_MIN)) { - result = buildAggregateMaxMin(ctx, aggregate, var_expr); - } else { - // get the aggregated value - Expression* current_aggregate = nullptr; - if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - current_aggregate = AddLoadAvgArray(ctx); - } else { - current_aggregate = AddGetAggValue(ctx); - } - - // the operators below take care of null inputs - Expression* aggregate_expr = nullptr; - switch (aggregate->_aggreaget_op) { - case JIT_AGGREGATE_AVG: - aggregate_expr = buildAggregateAvg(ctx, aggregate, current_aggregate, var_expr); - break; - - case JIT_AGGREGATE_SUM: - aggregate_expr = buildAggregateSum(ctx, aggregate, current_aggregate, var_expr); - break; - - case JIT_AGGREGATE_COUNT: - aggregate_expr = buildAggregateCount(ctx, aggregate, current_aggregate); - break; - - default: - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "JIT Compile", "Invalid aggregate operator %d", (int)aggregate->_aggreaget_op); - break; - } - - // write back the sum to the aggregated value/array - if (aggregate_expr != nullptr) { - if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - AddSaveAvgArray(ctx, aggregate_expr); - } else { - AddSetAggValue(ctx, aggregate_expr); - } - result = true; - } else { - // cleanup - delete current_aggregate; - delete var_expr; - MOT_LOG_TRACE("Failed to generate aggregate AVG/SUM/COUNT code"); - } - } - - return result; -} - -bool buildAggregateRow(JitTvmCodeGenContext* ctx, JitAggregate* aggregate, Instruction* row, BasicBlock* next_block) -{ - bool result = false; - - // extract the aggregated column - Expression* expr = AddReadDatumColumn(ctx, aggregate->_table, row, aggregate->_table_column_id, 0); - - // we first check if we have DISTINCT modifier - if (aggregate->_distinct) { - // check row is distinct in state set (managed on the current jit context) - // emit code to convert column to primitive data type, add value to distinct set and verify - // if value is not distinct then do not use this row in aggregation - JIT_IF_BEGIN(test_distinct_value) - Instruction* res = AddInsertDistinctItem(ctx, aggregate->_element_type, expr); - JIT_IF_EVAL_NOT(res) - IssueDebugLog("Value is not distinct, skipping aggregated row"); - ctx->_builder->CreateBr(next_block); - JIT_IF_END() - } - - // aggregate row - IssueDebugLog("Aggregating row into inner value"); - result = buildAggregateTuple(ctx, aggregate, expr); - if (result) { - // update number of rows processes - buildIncrementRowsProcessed(ctx); - } - - return result; -} - -void buildAggregateResult(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate) -{ - // in case of average we compute it when aggregate loop is done - if (aggregate->_aggreaget_op == JIT_AGGREGATE_AVG) { - Instruction* avg_value = AddComputeAvgFromArray( - ctx, aggregate->_avg_element_type); // we infer this during agg op analysis, but don't save it... - AddWriteTupleDatum(ctx, 0, avg_value); // we alway aggregate to slot tuple 0 - } else { - Expression* count_expr = AddGetAggValue(ctx); - Instruction* count_value = buildExpression(ctx, count_expr); - AddWriteTupleDatum(ctx, 0, count_value); // we alway aggregate to slot tuple 0 - } - - // we take the opportunity to cleanup as well - if (aggregate->_distinct) { - AddDestroyDistinctSet(ctx, aggregate->_element_type); - } -} - -void buildCheckLimit(JitTvmCodeGenContext* ctx, int limit_count) -{ - // if a limit clause exists, then increment limit counter and check if reached limit - if (limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - Instruction* current_limit_count = AddGetStateLimitCounter(ctx); - Instruction* limit_count_inst = JIT_CONST(limit_count); - JIT_IF_EVAL_CMP(current_limit_count, limit_count_inst, JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - // cleanup and signal scan ended (it is safe to cleanup even if not initialized, so we cleanup everything) - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); - AddSetScanEnded(ctx, 1); - JIT_IF_END() - } -} - -bool selectJoinRows( - JitTvmCodeGenContext* ctx, Instruction* outer_row_copy, Instruction* inner_row, JitJoinPlan* plan, int* max_arg) -{ - // select inner and outer row expressions into result tuple (no aggregate because aggregate is not stateful) - IssueDebugLog("Retrieved row from state iterator, beginning to select columns into result tuple"); - if (!selectRowColumns(ctx, outer_row_copy, &plan->_select_exprs, max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Inner Point JOIN query: failed to select outer row expressions"); - return false; - } - if (!selectRowColumns(ctx, inner_row, &plan->_select_exprs, max_arg, JIT_RANGE_SCAN_INNER)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Inner Point JOIN query: failed to select outer row expressions"); - return false; - } - return true; -} -} // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.h deleted file mode 100644 index 60d72304b..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_blocks.h - * Helpers to generate compound TVM-jitted code. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_blocks.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_BLOCKS_H -#define JIT_TVM_BLOCKS_H - -/* - * ATTENTION: Be sure to include jit_tvm_query.h before anything else because of libintl.h - * See jit_tvm_query.h for more details. - */ -#include "jit_tvm_query.h" -#include "jit_plan.h" - -namespace JitExec { -void CreateJittedFunction(JitTvmCodeGenContext* ctx, const char* function_name, const char* query_string); - -void buildIsSoftMemoryLimitReached(JitTvmCodeGenContext* ctx); - -void buildWriteRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row, bool isPKey, JitTvmRuntimeCursor* cursor); - -void buildResetRowsProcessed(JitTvmCodeGenContext* ctx); - -void buildIncrementRowsProcessed(JitTvmCodeGenContext* ctx); - -tvm::Instruction* buildCreateNewRow(JitTvmCodeGenContext* ctx); - -tvm::Instruction* buildSearchRow( - JitTvmCodeGenContext* ctx, MOT::AccessType access_type, JitRangeScanType range_scan_type, int subQueryIndex = -1); - -bool buildFilterRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row, JitFilterArray* filters, int* max_arg, - tvm::BasicBlock* next_block); - -void buildInsertRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row); - -void buildDeleteRow(JitTvmCodeGenContext* ctx); - -tvm::Instruction* buildGetRowFromIterator(JitTvmCodeGenContext* ctx, tvm::BasicBlock* endLoopBlock, - MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, JitTvmRuntimeCursor* cursor, - JitRangeScanType range_scan_type, int subQueryIndex = -1); - -bool buildPointScan(JitTvmCodeGenContext* ctx, JitColumnExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, tvm::Instruction* outer_row, int expr_count = -1, int subQueryIndex = -1); - -bool writeRowColumns( - JitTvmCodeGenContext* ctx, tvm::Instruction* row, JitColumnExprArray* expr_array, int* max_arg, bool is_update); - -bool selectRowColumns(JitTvmCodeGenContext* ctx, tvm::Instruction* row, JitSelectExprArray* expr_array, int* max_arg, - JitRangeScanType range_scan_type, int subQueryIndex = -1); - -tvm::Instruction* buildPrepareStateScanRow(JitTvmCodeGenContext* ctx, JitIndexScan* index_scan, - JitRangeScanType range_scan_type, MOT::AccessType access_mode, int* max_arg, tvm::Instruction* outer_row, - tvm::BasicBlock* next_block, tvm::BasicBlock** loop_block); - -JitTvmRuntimeCursor buildRangeCursor(JitTvmCodeGenContext* ctx, JitIndexScan* indexScan, int* maxArg, - JitRangeScanType rangeScanType, JitIndexScanDirection indexScanDirection, tvm::Instruction* outerRow, - int subQueryIndex = -1); - -bool prepareAggregate(JitTvmCodeGenContext* ctx, JitAggregate* aggregate); - -bool buildAggregateRow( - JitTvmCodeGenContext* ctx, JitAggregate* aggregate, tvm::Instruction* row, tvm::BasicBlock* next_block); - -void buildAggregateResult(JitTvmCodeGenContext* ctx, const JitAggregate* aggregate); - -void buildCheckLimit(JitTvmCodeGenContext* ctx, int limit_count); - -bool selectJoinRows(JitTvmCodeGenContext* ctx, tvm::Instruction* outer_row_copy, tvm::Instruction* inner_row, - JitJoinPlan* plan, int* max_arg); -} // namespace JitExec - -#endif /* JIT_TVM_BLOCKS_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_funcs.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm_funcs.h deleted file mode 100644 index 4ddca70f9..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_funcs.h +++ /dev/null @@ -1,3195 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_funcs.h - * TVM-jitted query codegen helpers. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_funcs.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_FUNCS_H -#define JIT_TVM_FUNCS_H - -/* - * ATTENTION: Be sure to include jit_tvm_query.h before anything else because of libintl.h - * See jit_tvm_query.h for more details. - */ -#include "jit_tvm_query.h" -#include "jit_plan_expr.h" -#include "logger.h" - -namespace JitExec { -/** @define The NULL datum. */ -#define NULL_DATUM PointerGetDatum(nullptr) - -/** @brief Gets a key from the execution context. */ -inline MOT::Key* getExecContextKey(tvm::ExecContext* exec_context, JitRangeIteratorType range_itr_type, - JitRangeScanType range_scan_type, int subQueryIndex) -{ - MOT::Key* key = nullptr; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { - key = exec_context->_jit_context->m_innerEndIteratorKey; - } else { - key = exec_context->_jit_context->m_innerSearchKey; - } - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { - key = exec_context->_jit_context->m_endIteratorKey; - } else { - key = exec_context->_jit_context->m_searchKey; - } - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (range_itr_type == JIT_RANGE_ITERATOR_END) { - key = exec_context->_jit_context->m_subQueryData[subQueryIndex].m_endIteratorKey; - } else { - key = exec_context->_jit_context->m_subQueryData[subQueryIndex].m_searchKey; - } - } - return key; -} - -/** @class VarExpression */ -class VarExpression : public tvm::Expression { -public: - VarExpression(MOT::Table* table, tvm::Instruction* row_inst, int table_colid, int arg_pos) - : Expression(tvm::Expression::CannotFail), - _table(table), - _row_inst(row_inst), - _table_colid(table_colid), - _arg_pos(arg_pos) - {} - - ~VarExpression() final - { - _table = nullptr; - _row_inst = nullptr; - } - - Datum eval(tvm::ExecContext* exec_context) final - { - exec_context->_expr_rc = MOT_NO_ERROR; - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - return readDatumColumn(_table, row, _table_colid, _arg_pos); - } - - void dump() final - { - (void)fprintf(stderr, "readDatumColumn(%%table, %%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ", table_colid=%d, arg_pos=%d)", _table_colid, _arg_pos); - } - -private: - MOT::Table* _table; - tvm::Instruction* _row_inst; - int _table_colid; // already zero-based - int _arg_pos; -}; - -/** @class UnaryOperator */ -class UnaryOperator : public tvm::Expression { -public: - Datum eval(tvm::ExecContext* exec_context) final - { - Datum result = NULL_DATUM; - Datum arg = _sub_expr->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = evalOperator(exec_context, arg); - } - return result; - } - - void dump() final - { - (void)fprintf(stderr, "%s(", _name.c_str()); - _sub_expr->dump(); - (void)fprintf(stderr, ")"); - } - -protected: - UnaryOperator(const char* name, tvm::Expression* sub_expr) - : Expression(tvm::Expression::CanFail), _name(name), _sub_expr(sub_expr) - {} - - ~UnaryOperator() noexcept override - { - MOT_LOG_DEBUG("Deleting sub-expr %p in unary operator %s at %p", _sub_expr, _name.c_str(), this); - delete _sub_expr; - } - - virtual Datum evalOperator(tvm::ExecContext* exec_context, Datum arg) = 0; - -private: - std::string _name; - tvm::Expression* _sub_expr; -}; - -/** @class UnaryCastOperator */ -class UnaryCastOperator : public UnaryOperator { -protected: - UnaryCastOperator(const char* name, tvm::Expression* sub_expr, int arg_pos) - : UnaryOperator(name, sub_expr), _arg_pos(arg_pos) - {} - - ~UnaryCastOperator() override - {} - - Datum evalOperator(tvm::ExecContext* exec_context, Datum arg) final - { - Datum result = NULL_DATUM; - exec_context->_expr_rc = MOT_NO_ERROR; - int isnull = getExprArgIsNull(_arg_pos); - if (!isnull) { - result = evalCastOperator(exec_context, arg); - } - return result; - } - - virtual Datum evalCastOperator(tvm::ExecContext* exec_context, Datum arg) = 0; - -private: - int _arg_pos; -}; - -/** @class BinaryOperator */ -class BinaryOperator : public tvm::Expression { -public: - Datum eval(tvm::ExecContext* exec_context) final - { - Datum result = NULL_DATUM; - Datum lhs_arg = _lhs_sub_expr->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - Datum rhs_arg = _rhs_sub_expr->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = evalOperator(exec_context, lhs_arg, rhs_arg); - } - } - return result; - } - - void dump() override - { - (void)fprintf(stderr, "%s(", _name.c_str()); - _lhs_sub_expr->dump(); - (void)fprintf(stderr, ", "); - _rhs_sub_expr->dump(); - (void)fprintf(stderr, ")"); - } - -protected: - BinaryOperator(const char* name, tvm::Expression* lhs_sub_expr, Expression* rhs_sub_expr) - : Expression(tvm::Expression::CanFail), _name(name), _lhs_sub_expr(lhs_sub_expr), _rhs_sub_expr(rhs_sub_expr) - {} - - ~BinaryOperator() noexcept override - { - MOT_LOG_DEBUG("Deleting lhs-sub-expr %p and rhs-sub-expr %p in binary operator %s at %p", - _lhs_sub_expr, - _rhs_sub_expr, - _name.c_str(), - this); - delete _lhs_sub_expr; - delete _rhs_sub_expr; - } - - virtual Datum evalOperator(tvm::ExecContext* exec_context, Datum lhs_arg, Datum rhs_arg) = 0; - -private: - std::string _name; - Expression* _lhs_sub_expr; - Expression* _rhs_sub_expr; -}; - -/** @class BinaryCastOperator */ -class BinaryCastOperator : public BinaryOperator { -protected: - BinaryCastOperator(const char* name, tvm::Expression* lhs_sub_expr, tvm::Expression* rhs_sub_expr, int arg_pos) - : BinaryOperator(name, lhs_sub_expr, rhs_sub_expr), _arg_pos(arg_pos) - {} - - ~BinaryCastOperator() override - {} - - Datum evalOperator(tvm::ExecContext* exec_context, Datum lhs_arg, Datum rhs_arg) final - { - Datum result = NULL_DATUM; - exec_context->_expr_rc = MOT_NO_ERROR; - int isnull = getExprArgIsNull(_arg_pos); - if (!isnull) { - result = evalCastOperator(exec_context, lhs_arg, rhs_arg); - } - return result; - } - - virtual Datum evalCastOperator(tvm::ExecContext* exec_context, Datum lhs_arg, Datum rhs_arg) = 0; - -private: - int _arg_pos; -}; - -/** @class TernaryOperator */ -class TernaryOperator : public tvm::Expression { -public: - Datum eval(tvm::ExecContext* exec_context) final - { - Datum result = NULL_DATUM; - exec_context->_expr_rc = MOT_NO_ERROR; - Datum arg1 = _sub_expr1->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - Datum arg2 = _sub_expr2->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - Datum arg3 = _sub_expr3->eval(exec_context); - if (exec_context->_expr_rc == MOT_NO_ERROR) { - result = evalOperator(exec_context, arg1, arg2, arg3); - } - } - } - return result; - } - - void dump() override - { - (void)fprintf(stderr, "%s(", _name.c_str()); - _sub_expr1->dump(); - (void)fprintf(stderr, ", "); - _sub_expr2->dump(); - (void)fprintf(stderr, ", "); - _sub_expr3->dump(); - (void)fprintf(stderr, ")"); - } - -protected: - TernaryOperator( - const char* name, tvm::Expression* sub_expr1, tvm::Expression* sub_expr2, tvm::Expression* sub_expr3) - : Expression(tvm::Expression::CanFail), - _name(name), - _sub_expr1(sub_expr1), - _sub_expr2(sub_expr2), - _sub_expr3(sub_expr3) - {} - - ~TernaryOperator() noexcept override - { - MOT_LOG_DEBUG("Deleting sub-expr1 %p, sub-expr2 %p and sub-expr3 %p in ternary operator %s at %p", - _sub_expr1, - _sub_expr2, - _sub_expr3, - _name.c_str(), - this); - delete _sub_expr1; - delete _sub_expr2; - delete _sub_expr3; - } - - virtual Datum evalOperator(tvm::ExecContext* exec_context, Datum arg1, Datum arg2, Datum arg3) = 0; - -private: - std::string _name; - Expression* _sub_expr1; - Expression* _sub_expr2; - Expression* _sub_expr3; -}; - -/** @class TernaryCastOperator */ -class TernaryCastOperator : public TernaryOperator { -protected: - TernaryCastOperator(const char* name, tvm::Expression* sub_expr1, tvm::Expression* sub_expr2, - tvm::Expression* sub_expr3, int arg_pos) - : TernaryOperator(name, sub_expr1, sub_expr2, sub_expr3), _arg_pos(arg_pos) - {} - - ~TernaryCastOperator() override - {} - - Datum evalOperator(tvm::ExecContext* exec_context, Datum arg1, Datum arg2, Datum arg3) final - { - Datum result = NULL_DATUM; - exec_context->_expr_rc = MOT_NO_ERROR; - int isnull = getExprArgIsNull(_arg_pos); - if (!isnull) { - result = evalCastOperator(exec_context, arg1, arg2, arg3); - } - return result; - } - - virtual Datum evalCastOperator(tvm::ExecContext* exec_context, Datum arg1, Datum arg2, Datum arg3) = 0; - -private: - int _arg_pos; -}; - -/** @define Invoke PG operator with 1 argument. */ -#define INVOKE_OPERATOR1(funcid, name, arg) \ - PG_TRY(); \ - { \ - result = DirectFunctionCall1(name, arg); \ - } \ - PG_CATCH(); \ - { \ - MOT_REPORT_ERROR(MOT_ERROR_SYSTEM_FAILURE, \ - "Execute JIT", \ - "Failed to execute PG operator %s (proc-id: %u)", \ - #name, \ - (unsigned)funcid); \ - exec_context->_expr_rc = MOT_ERROR_SYSTEM_FAILURE; \ - } \ - PG_END_TRY(); - -/** @define Invoke PG operator with 2 arguments. */ -#define INVOKE_OPERATOR2(funcid, name, arg1, arg2) \ - PG_TRY(); \ - { \ - result = DirectFunctionCall2(name, arg1, arg2); \ - } \ - PG_CATCH(); \ - { \ - MOT_REPORT_ERROR(MOT_ERROR_SYSTEM_FAILURE, \ - "Execute JIT", \ - "Failed to execute PG operator %s (proc-id: %u)", \ - #name, \ - (unsigned)funcid); \ - exec_context->_expr_rc = MOT_ERROR_SYSTEM_FAILURE; \ - } \ - PG_END_TRY(); - -/** @define Invoke PG operator with 3 arguments. */ -#define INVOKE_OPERATOR3(funcid, name, arg1, arg2, arg3) \ - PG_TRY(); \ - { \ - result = DirectFunctionCall3(name, arg1, arg2, arg3); \ - } \ - PG_CATCH(); \ - { \ - MOT_REPORT_ERROR(MOT_ERROR_SYSTEM_FAILURE, \ - "Execute JIT", \ - "Failed to execute PG operator %s (proc-id: %u)", \ - #name, \ - (unsigned)funcid); \ - exec_context->_expr_rc = MOT_ERROR_SYSTEM_FAILURE; \ - } \ - PG_END_TRY(); - -#define APPLY_UNARY_OPERATOR(funcid, name) \ - class name##Operator : public UnaryOperator { \ - public: \ - explicit name##Operator(tvm::Expression* sub_expr) : UnaryOperator(#name, sub_expr) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalOperator(tvm::ExecContext* exec_context, Datum arg) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking unary operator: " #name); \ - INVOKE_OPERATOR1(funcid, name, arg); \ - return result; \ - } \ - }; - -#define APPLY_UNARY_CAST_OPERATOR(funcid, name) \ - class name##Operator : public UnaryCastOperator { \ - public: \ - name##Operator(tvm::Expression* sub_expr, int arg_pos) : UnaryCastOperator(#name, sub_expr, arg_pos) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalCastOperator(tvm::ExecContext* exec_context, Datum arg) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking unary cast operator: " #name); \ - INVOKE_OPERATOR1(funcid, name, arg); \ - return result; \ - } \ - }; - -#define APPLY_BINARY_OPERATOR(funcid, name) \ - class name##Operator : public BinaryOperator { \ - public: \ - name##Operator(tvm::Expression* lhs_sub_expr, tvm::Expression* rhs_sub_expr) \ - : BinaryOperator(#name, lhs_sub_expr, rhs_sub_expr) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalOperator(tvm::ExecContext* exec_context, Datum lhs_arg, Datum rhs_arg) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking binary operator: " #name); \ - INVOKE_OPERATOR2(funcid, name, lhs_arg, rhs_arg); \ - return result; \ - } \ - }; - -#define APPLY_BINARY_CAST_OPERATOR(funcid, name) \ - class name##Operator : public BinaryCastOperator { \ - public: \ - name##Operator(tvm::Expression* lhs_sub_expr, tvm::Expression* rhs_sub_expr, int arg_pos) \ - : BinaryCastOperator(#name, lhs_sub_expr, rhs_sub_expr, arg_pos) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalCastOperator(tvm::ExecContext* exec_context, Datum lhs_arg, Datum rhs_arg) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking binary cast operator: " #name); \ - INVOKE_OPERATOR2(funcid, name, lhs_arg, rhs_arg); \ - return result; \ - } \ - }; - -#define APPLY_TERNARY_OPERATOR(funcid, name) \ - class name##Operator : public TernaryOperator { \ - public: \ - name##Operator(tvm::Expression* sub_expr1, tvm::Expression* sub_expr2, tvm::Expression* sub_expr3) \ - : TernaryOperator(#name, sub_expr1, sub_expr2, sub_expr3) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalOperator(tvm::ExecContext* exec_context, Datum arg1, Datum arg2, Datum arg3) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking ternary operator: " #name); \ - INVOKE_OPERATOR3(funcid, name, arg1, arg2, arg3); \ - return result; \ - } \ - }; - -#define APPLY_TERNARY_CAST_OPERATOR(funcid, name) \ - class name##Operator : public TernaryCastOperator { \ - public: \ - name##Operator( \ - tvm::Expression* sub_expr1, tvm::Expression* sub_expr2, tvm::Expression* sub_expr3, int arg_pos) \ - : TernaryCastOperator(#name, sub_expr1, sub_expr2, sub_expr3, arg_pos) \ - {} \ - ~name##Operator() final \ - {} \ - \ - protected: \ - Datum evalCastOperator(tvm::ExecContext* exec_context, Datum arg1, Datum arg2, Datum arg3) final \ - { \ - Datum result = NULL_DATUM; \ - exec_context->_expr_rc = MOT_NO_ERROR; \ - MOT_LOG_DEBUG("Invoking ternary cast operator: " #name); \ - INVOKE_OPERATOR3(funcid, name, arg1, arg2, arg3); \ - return result; \ - } \ - }; - -APPLY_OPERATORS() - -#undef APPLY_UNARY_OPERATOR -#undef APPLY_BINARY_OPERATOR -#undef APPLY_TERNARY_OPERATOR -#undef APPLY_UNARY_CAST_OPERATOR -#undef APPLY_BINARY_CAST_OPERATOR -#undef APPLY_TERNARY_CAST_OPERATOR - -/** @class IsSoftMemoryLimitReachedInstruction */ -class IsSoftMemoryLimitReachedInstruction : public tvm::Instruction { -public: - IsSoftMemoryLimitReachedInstruction() - {} - - ~IsSoftMemoryLimitReachedInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)isSoftMemoryLimitReached(); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "isSoftMemoryLimitReached()"); - } -}; - -/** InitSearchKeyInstruction */ -class InitSearchKeyInstruction : public tvm::Instruction { -public: - explicit InitSearchKeyInstruction(JitRangeScanType range_scan_type, int subQueryIndex) - : Instruction(tvm::Instruction::Void), _range_scan_type(range_scan_type), m_subQueryIndex(subQueryIndex) - {} - - ~InitSearchKeyInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - InitKey(exec_context->_jit_context->m_innerSearchKey, exec_context->_jit_context->m_innerIndex); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - InitKey(exec_context->_jit_context->m_searchKey, exec_context->_jit_context->m_index); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitContext::SubQueryData* subQueryData = &exec_context->_jit_context->m_subQueryData[m_subQueryIndex]; - InitKey(subQueryData->m_searchKey, subQueryData->m_index); - } else { - return (uint64_t)MOT::RC_ERROR; - } - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "InitKey(%%inner_search_key, %%inner_index)"); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "InitKey(%%search_key, %%index)"); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf( - stderr, "InitKey(%%sub_query_key[%d], %%sub_query_index[%d])", m_subQueryIndex, m_subQueryIndex); - } - } - -private: - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** GetColumnAtInstruction */ -class GetColumnAtInstruction : public tvm::Instruction { -public: - GetColumnAtInstruction(int table_colid, JitRangeScanType range_scan_type, int subQueryIndex) - : _table_colid(table_colid), _range_scan_type(range_scan_type), m_subQueryIndex(subQueryIndex) - {} - - ~GetColumnAtInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Column* column = nullptr; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - column = getColumnAt(exec_context->_jit_context->m_innerTable, _table_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - column = getColumnAt(exec_context->_jit_context->m_table, _table_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - column = getColumnAt(exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_table, _table_colid); - } else { - return (uint64_t) nullptr; - } - return (uint64_t)column; - } - - void DumpImpl() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "%%column = %%inner_table->columns[%d]", _table_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "%%column = %%table->columns[%d]", _table_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, "%%column = %%sub_query[%d].table->columns[%d]", m_subQueryIndex, _table_colid); - } - } - -private: - int _table_colid; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** SetExprArgIsNullInstruction */ -class SetExprArgIsNullInstruction : public tvm::Instruction { -public: - SetExprArgIsNullInstruction(int arg_pos, int is_null) - : Instruction(tvm::Instruction::Void), _arg_pos(arg_pos), _is_null(is_null) - {} - - ~SetExprArgIsNullInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - setExprArgIsNull(_arg_pos, _is_null); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "setExprArgIsNull(%d, %d)", _arg_pos, _is_null); - } - -private: - int _arg_pos; - int _is_null; -}; - -/** GetExprArgIsNullInstruction */ -class GetExprArgIsNullInstruction : public tvm::Instruction { -public: - explicit GetExprArgIsNullInstruction(int arg_pos) : _arg_pos(arg_pos) - {} - - ~GetExprArgIsNullInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)getExprArgIsNull(_arg_pos); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "getExprArgIsNull(%d)", _arg_pos); - } - -private: - int _arg_pos; -}; - -/** WriteDatumColumnInstruction */ -class WriteDatumColumnInstruction : public tvm::Instruction { -public: - WriteDatumColumnInstruction(Instruction* row_inst, Instruction* column_inst, Instruction* sub_expr) - : Instruction(tvm::Instruction::Void), _row_inst(row_inst), _column_inst(column_inst), _sub_expr(sub_expr) - { - AddSubInstruction(row_inst); - AddSubInstruction(column_inst); - AddSubInstruction(sub_expr); - } - - ~WriteDatumColumnInstruction() final - { - _row_inst = nullptr; - _column_inst = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - Datum arg = (Datum)_sub_expr->Exec(exec_context); - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - MOT::Column* column = (MOT::Column*)_column_inst->Exec(exec_context); - writeDatumColumn(row, column, arg); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "writeDatumColumn(%%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ", %%column="); - _column_inst->Dump(); - (void)fprintf(stderr, ", "); - _sub_expr->Dump(); - (void)fprintf(stderr, ")"); - } - -private: - tvm::Instruction* _row_inst; - tvm::Instruction* _column_inst; - tvm::Instruction* _sub_expr; -}; - -/** BuildDatumKeyInstruction */ -class BuildDatumKeyInstruction : public tvm::Instruction { -public: - BuildDatumKeyInstruction(tvm::Instruction* column_inst, tvm::Instruction* sub_expr, int index_colid, int offset, - int size, int value_type, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, - int subQueryIndex) - : Instruction(tvm::Instruction::Void), - _column_inst(column_inst), - _sub_expr(sub_expr), - _index_colid(index_colid), - _offset(offset), - _size(size), - _value_type(value_type), - _range_itr_type(range_itr_type), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - { - AddSubInstruction(_column_inst); - AddSubInstruction(_sub_expr); - } - - ~BuildDatumKeyInstruction() final - { - _column_inst = nullptr; - _sub_expr = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - Datum arg = (Datum)_sub_expr->Exec(exec_context); - MOT::Column* column = (MOT::Column*)_column_inst->Exec(exec_context); - MOT::Key* key = getExecContextKey(exec_context, _range_itr_type, _range_scan_type, m_subQueryIndex); - buildDatumKey(column, key, arg, _index_colid, _offset, _size, _value_type); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "buildDatumKey(%%column="); - _column_inst->Dump(); - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - if (_range_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, ", %%inner_end_iterator_key, "); - } else { - (void)fprintf(stderr, ", %%inner_search_key, "); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (_range_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, ", %%end_iterator_key, "); - } else { - (void)fprintf(stderr, ", %%search_key, "); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (_range_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, ", %%sub_query[%d].end_iterator_key, ", m_subQueryIndex); - } else { - (void)fprintf(stderr, ", %%sub_query[%d].search_key, ", m_subQueryIndex); - } - } - _sub_expr->Dump(); - (void)fprintf( - stderr, ", index_colid=%d, offset=%d, size=%d, value_type=%d)", _index_colid, _offset, _size, _value_type); - } - -private: - tvm::Instruction* _column_inst; - tvm::Instruction* _sub_expr; - int _index_colid; - int _offset; - int _size; - int _value_type; - JitRangeIteratorType _range_itr_type; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** SetBitInstruction */ -class SetBitInstruction : public tvm::Instruction { -public: - explicit SetBitInstruction(int col) : Instruction(tvm::Instruction::Void), _col(col) - {} - - ~SetBitInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - setBit(exec_context->_jit_context->m_bitmapSet, _col); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "setBit(%%bitmap_set, col=%d)", _col); - } - -private: - int _col; -}; - -/** @class ResetBitmapSetInstruction */ -class ResetBitmapSetInstruction : public tvm::Instruction { -public: - ResetBitmapSetInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ResetBitmapSetInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - resetBitmapSet(exec_context->_jit_context->m_bitmapSet); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "resetBitmapSet(%%bitmap_set)"); - } -}; - -/** @class WriteRowInstruction */ -class WriteRowInstruction : public tvm::Instruction { -public: - explicit WriteRowInstruction(tvm::Instruction* row_inst) : _row_inst(row_inst) - { - AddSubInstruction(_row_inst); - } - - ~WriteRowInstruction() final - { - _row_inst = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - return (uint64_t)writeRow(row, exec_context->_jit_context->m_bitmapSet); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "writeRow(%%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ", %%bitmap_set)"); - } - -private: - tvm::Instruction* _row_inst; -}; - -/** SearchRowInstruction */ -class SearchRowInstruction : public tvm::Instruction { -public: - SearchRowInstruction(MOT::AccessType access_mode_value, JitRangeScanType range_scan_type, int subQueryIndex) - : _access_mode_value(access_mode_value), _range_scan_type(range_scan_type), m_subQueryIndex(subQueryIndex) - {} - - ~SearchRowInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Row* row = nullptr; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - row = searchRow(exec_context->_jit_context->m_innerTable, - exec_context->_jit_context->m_innerSearchKey, - _access_mode_value); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - row = searchRow( - exec_context->_jit_context->m_table, exec_context->_jit_context->m_searchKey, _access_mode_value); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitContext::SubQueryData* subQueryData = &exec_context->_jit_context->m_subQueryData[m_subQueryIndex]; - row = searchRow(subQueryData->m_table, subQueryData->m_searchKey, _access_mode_value); - } else { - return (uint64_t) nullptr; - } - return (uint64_t)row; - } - - void DumpImpl() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, - "%%row = searchRow(%%inner_table, %%inner_search_key, access_mode_value=%d)", - (int)_access_mode_value); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf( - stderr, "%%row = searchRow(%%table, %%search_key, access_mode_value=%d)", (int)_access_mode_value); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, - "%%row = searchRow(%%sub_query[%d].table, %%sub_query[%d].search_key, access_mode_value=%d)", - m_subQueryIndex, - m_subQueryIndex, - (int)_access_mode_value); - } - } - -private: - MOT::AccessType _access_mode_value; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class CreateNewRowInstruction */ -class CreateNewRowInstruction : public tvm::Instruction { -public: - CreateNewRowInstruction() - {} - - ~CreateNewRowInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Row* row = createNewRow(exec_context->_jit_context->m_table); - return (uint64_t)row; - } - - void DumpImpl() final - { - (void)fprintf(stderr, "%%row = createNewRow(%%table)"); - } -}; - -/** @class InsertRowInstruction */ -class InsertRowInstruction : public tvm::Instruction { -public: - explicit InsertRowInstruction(tvm::Instruction* row_inst) : _row_inst(row_inst) - { - AddSubInstruction(_row_inst); - } - - ~InsertRowInstruction() final - { - _row_inst = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - return (uint64_t)insertRow(exec_context->_jit_context->m_table, row); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "insertRow(%%table, %%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ")"); - } - -private: - tvm::Instruction* _row_inst; -}; - -/** @class DeleteRowInstruction */ -class DeleteRowInstruction : public tvm::Instruction { -public: - DeleteRowInstruction() - {} - ~DeleteRowInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)deleteRow(); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "deleteRow()"); - } -}; - -/** @class SetRowNullBitsInstruction */ -class SetRowNullBitsInstruction : public tvm::Instruction { -public: - explicit SetRowNullBitsInstruction(tvm::Instruction* row_inst) - : Instruction(tvm::Instruction::Void), _row_inst(row_inst) - { - AddSubInstruction(_row_inst); - } - - ~SetRowNullBitsInstruction() final - { - _row_inst = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - setRowNullBits(exec_context->_jit_context->m_table, row); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "setRowNullBits(%%table, %%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ")"); - } - -private: - Instruction* _row_inst; -}; - -/** @class SetExprResultNullBitInstruction */ -class SetExprResultNullBitInstruction : public tvm::Instruction { -public: - SetExprResultNullBitInstruction(tvm::Instruction* row_inst, int table_colid) - : _row_inst(row_inst), _table_colid(table_colid) - { - AddSubInstruction(_row_inst); - } - - ~SetExprResultNullBitInstruction() final - { - _row_inst = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - // always performed on main table (there is no inner table in UPDATE command) - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - return (uint64_t)setExprResultNullBit(exec_context->_jit_context->m_table, row, _table_colid); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "setExprResultNullBit(%%table, %%row="); - _row_inst->Dump(); - (void)fprintf(stderr, ", table_colid=%d)", _table_colid); - } - -private: - Instruction* _row_inst; - int _table_colid; -}; - -/** @class ExecClearTupleInstruction */ -class ExecClearTupleInstruction : public tvm::Instruction { -public: - ExecClearTupleInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ExecClearTupleInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - execClearTuple(exec_context->_slot); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "execClearTuple(%%slot)"); - } -}; - -/** @class ExecStoreVirtualTupleInstruction */ -class ExecStoreVirtualTupleInstruction : public tvm::Instruction { -public: - ExecStoreVirtualTupleInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ExecStoreVirtualTupleInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - execStoreVirtualTuple(exec_context->_slot); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "execStoreVirtualTuple(%%slot)"); - } -}; - -/** @class SelectColumnInstruction */ -class SelectColumnInstruction : public tvm::Instruction { -public: - SelectColumnInstruction(tvm::Instruction* row_inst, int table_colid, int tuple_colid, - JitRangeScanType range_scan_type, int subQueryIndex) - : Instruction(tvm::Instruction::Void), - _row_inst(row_inst), - _table_colid(table_colid), - _tuple_colid(tuple_colid), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - { - AddSubInstruction(_row_inst); - } - - ~SelectColumnInstruction() final - { - _row_inst = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::Row* row = (MOT::Row*)_row_inst->Exec(exec_context); - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - selectColumn( - exec_context->_jit_context->m_innerTable, row, exec_context->_slot, _table_colid, _tuple_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - selectColumn(exec_context->_jit_context->m_table, row, exec_context->_slot, _table_colid, _tuple_colid); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitContext::SubQueryData* subQueryData = &exec_context->_jit_context->m_subQueryData[m_subQueryIndex]; - selectColumn(subQueryData->m_table, row, subQueryData->m_slot, _table_colid, _tuple_colid); - } else { - return (uint64_t)MOT::RC_ERROR; - } - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "selectColumn(%%inner_table, %%row="); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "selectColumn(%%table, %%row="); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, "selectColumn(%%sub_query[%d].table, %%row=", m_subQueryIndex); - } - _row_inst->Dump(); - if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, - ", %%sub_query[%d].slot, table_colid=%d, tuple_colid=%d)", - m_subQueryIndex, - _table_colid, - _tuple_colid); - } else { - (void)fprintf(stderr, ", %%slot, table_colid=%d, tuple_colid=%d)", _table_colid, _tuple_colid); - } - } - -private: - tvm::Instruction* _row_inst; - int _table_colid; - int _tuple_colid; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** - * @class SetTpProcessedInstruction. Sets the number of tuples processed from the number of rows - * processed. This communicates back to the caller a return value. - */ -class SetTpProcessedInstruction : public tvm::Instruction { -public: - /** @brief Constructor. */ - SetTpProcessedInstruction() : Instruction(tvm::Instruction::Void) - {} - - /** @brief Destructor. */ - ~SetTpProcessedInstruction() final - {} - - /** - * @brief Executes the instruction by setting the number of tuples processed. - * @param exec_context The execution context. - * @return Not used. - */ - uint64_t Exec(tvm::ExecContext* exec_context) final - { - setTpProcessed(exec_context->_tp_processed, exec_context->_rows_processed); - return (uint64_t)MOT::RC_OK; - } - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() final - { - (void)fprintf(stderr, "setTpProcessed(%%tp_processed, %%rows_processed)"); - } -}; - -/** - * @class SetScanEndedInstruction. Communicates back to the user whether a range scan has ended - * (i.e. whether there are no more tuples to report in this query). - */ -class SetScanEndedInstruction : public tvm::Instruction { -public: - /** - * @brief Constructor. - * @param result The scan-ended result. Zero means there are more tuples to report. One means the scan ended. - */ - explicit SetScanEndedInstruction(int result) : Instruction(tvm::Instruction::Void), _result(result) - {} - - /** @brief Destructor. */ - ~SetScanEndedInstruction() final - {} - - /** - * @brief Executes the instruction by setting the scan-ended value. - * @param exec_context The execution context. - * @return Not used. - */ - uint64_t Exec(tvm::ExecContext* exec_context) final - { - setScanEnded(exec_context->_scan_ended, _result); - return (uint64_t)MOT::RC_OK; - } - - /** @brief Dumps the instruction to the standard error stream. */ - void Dump() final - { - (void)fprintf(stderr, "setScanEnded(%%scan_ended, %d)", _result); - } - -private: - /** @var The scan-ended result. Zero means there are more tuples to report. One means the scan ended. */ - int _result; -}; - -/** @class ResetRowsProcessedInstruction */ -class ResetRowsProcessedInstruction : public tvm::Instruction { -public: - ResetRowsProcessedInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ResetRowsProcessedInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - exec_context->_rows_processed = 0; - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "%%rows_processed = 0"); - } -}; - -/** @class IncrementRowsProcessedInstruction */ -class IncrementRowsProcessedInstruction : public tvm::Instruction { -public: - IncrementRowsProcessedInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~IncrementRowsProcessedInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - ++exec_context->_rows_processed; - return 0; - } - - void Dump() final - { - (void)fprintf(stderr, "%%rows_processed += 1"); - } -}; - -/** @class CopyKeyInstruction */ -class CopyKeyInstruction : public tvm::Instruction { -public: - explicit CopyKeyInstruction(JitRangeScanType range_scan_type, int subQueryIndex) - : Instruction(tvm::Instruction::Void), _range_scan_type(range_scan_type), m_subQueryIndex(subQueryIndex) - {} - - ~CopyKeyInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - copyKey(exec_context->_jit_context->m_innerIndex, - exec_context->_jit_context->m_innerSearchKey, - exec_context->_jit_context->m_innerEndIteratorKey); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - copyKey(exec_context->_jit_context->m_index, - exec_context->_jit_context->m_searchKey, - exec_context->_jit_context->m_endIteratorKey); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - JitContext::SubQueryData* subQueryData = &exec_context->_jit_context->m_subQueryData[m_subQueryIndex]; - copyKey(subQueryData->m_index, subQueryData->m_searchKey, subQueryData->m_endIteratorKey); - } - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "copyKey(%%inner_index, %%inner_search_key, %%inner_end_iterator_key)"); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "copyKey(%%index, %%search_key, %%end_iterator_key)"); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, - "copyKey(%%sub_query[%d].index, %%sub_query[%d].search_key, %%sub_query[%d].end_iterator_key)", - m_subQueryIndex, - m_subQueryIndex, - m_subQueryIndex); - } - } - -private: - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class FillKeyPatternInstruction */ -class FillKeyPatternInstruction : public tvm::Instruction { -public: - FillKeyPatternInstruction(JitRangeIteratorType itr_type, unsigned char pattern, int offset, int size, - JitRangeScanType range_scan_type, int subQueryIndex) - : Instruction(tvm::Instruction::Void), - _itr_type(itr_type), - _pattern(pattern), - _offset(offset), - _size(size), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - {} - - ~FillKeyPatternInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::Key* key = getExecContextKey(exec_context, _itr_type, _range_scan_type, m_subQueryIndex); - FillKeyPattern(key, _pattern, _offset, _size); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, - "FillKeyPattern(%%inner_end_iterator_key, pattern=0x%X, offset=%d, size=%d)", - (unsigned)_pattern, - _offset, - _size); - } else { - (void)fprintf(stderr, - "FillKeyPattern(%%inner_search_key, pattern=0x%X, offset=%d, size=%d)", - (unsigned)_pattern, - _offset, - _size); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, - "FillKeyPattern(%%end_iterator_key, pattern=0x%X, offset=%d, size=%d)", - (unsigned)_pattern, - _offset, - _size); - } else { - (void)fprintf(stderr, - "FillKeyPattern(%%search_key, pattern=0x%X, offset=%d, size=%d)", - (unsigned)_pattern, - _offset, - _size); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, - "FillKeyPattern(%%sub_query[%d].end_iterator_key, pattern=0x%X, offset=%d, size=%d)", - m_subQueryIndex, - (unsigned)_pattern, - _offset, - _size); - } else { - (void)fprintf(stderr, - "FillKeyPattern(%%sub_query[%d].search_key, pattern=0x%X, offset=%d, size=%d)", - m_subQueryIndex, - (unsigned)_pattern, - _offset, - _size); - } - } - } - -private: - JitRangeIteratorType _itr_type; - unsigned char _pattern; - int _offset; - int _size; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class AdjustKeyInstruction */ -class AdjustKeyInstruction : public tvm::Instruction { -public: - AdjustKeyInstruction( - JitRangeIteratorType itr_type, unsigned char pattern, JitRangeScanType range_scan_type, int subQueryIndex) - : Instruction(tvm::Instruction::Void), - _itr_type(itr_type), - _pattern(pattern), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - {} - - ~AdjustKeyInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::Key* key = getExecContextKey(exec_context, _itr_type, _range_scan_type, m_subQueryIndex); - MOT::Index* index = nullptr; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - index = exec_context->_jit_context->m_innerIndex; - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - index = exec_context->_jit_context->m_index; - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - index = exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index; - } - adjustKey(key, index, _pattern); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf( - stderr, "adjustKey(%%inner_end_iterator_key, %%inner_index, pattern=0x%X)", (unsigned)_pattern); - } else { - (void)fprintf(stderr, "adjustKey(%%inner_search_key, %%inner_index, pattern=0x%X)", (unsigned)_pattern); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, "adjustKey(%%end_iterator_key, %%index, pattern=0x%X)", (unsigned)_pattern); - } else { - (void)fprintf(stderr, "adjustKey(%%search_key, %%index, pattern=0x%X)", (unsigned)_pattern); - } - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - if (_itr_type == JIT_RANGE_ITERATOR_END) { - (void)fprintf(stderr, - "adjustKey(%%sub_query[%d].end_iterator_key, %%sub_query[%d].index, pattern=0x%X)", - m_subQueryIndex, - m_subQueryIndex, - (unsigned)_pattern); - } else { - (void)fprintf(stderr, - "adjustKey(%%sub_query[%d].search_key, %%sub_query[%d].index, pattern=0x%X)", - m_subQueryIndex, - m_subQueryIndex, - (unsigned)_pattern); - } - } - } - -private: - JitRangeIteratorType _itr_type; - unsigned char _pattern; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class SearchIteratorInstruction */ -class SearchIteratorInstruction : public tvm::Instruction { -public: - SearchIteratorInstruction(JitIndexScanDirection index_scan_direction, JitRangeBoundMode range_bound_mode, - JitRangeScanType range_scan_type, int subQueryIndex) - : _index_scan_direction(index_scan_direction), - _range_bound_mode(range_bound_mode), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - {} - - ~SearchIteratorInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::IndexIterator* iterator = nullptr; - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int include_bound = (_range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - iterator = searchIterator(exec_context->_jit_context->m_innerIndex, - exec_context->_jit_context->m_innerSearchKey, - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - iterator = searchIterator(exec_context->_jit_context->m_index, - exec_context->_jit_context->m_searchKey, - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - iterator = searchIterator(exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index, - exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_searchKey, - forward_scan, - include_bound); - } - return (uint64_t)iterator; - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int include_bound = (_range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, - "searchIterator(%%inner_index, %%inner_search_key, forward_scan=%d, include_bound=%d)", - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, - "searchIterator(%%index, %%search_key, forward_scan=%d, include_bound=%d)", - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, - "searchIterator(%%sub_query[%d].index, %%sub_query[%d].search_key, forward_scan=%d, include_bound=%d)", - m_subQueryIndex, - m_subQueryIndex, - forward_scan, - include_bound); - } - } - -private: - JitIndexScanDirection _index_scan_direction; - JitRangeBoundMode _range_bound_mode; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class BeginIteratorInstruction */ -class BeginIteratorInstruction : public tvm::Instruction { -public: - BeginIteratorInstruction(JitRangeScanType rangeScanType, int subQueryIndex) - : m_rangeScanType(rangeScanType), m_subQueryIndex(subQueryIndex) - {} - - ~BeginIteratorInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::IndexIterator* iterator = nullptr; - if (m_rangeScanType == JIT_RANGE_SCAN_INNER) { - iterator = beginIterator(exec_context->_jit_context->m_innerIndex); - } else if (m_rangeScanType == JIT_RANGE_SCAN_MAIN) { - iterator = beginIterator(exec_context->_jit_context->m_index); - } else if (m_rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - iterator = beginIterator(exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index); - } - return (uint64_t)iterator; - } - - void DumpImpl() final - { - if (m_rangeScanType == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "beginIterator(%%inner_index)"); - } else if (m_rangeScanType == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "beginIterator(%%index)"); - } else if (m_rangeScanType == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, "beginIterator(%%sub_query[%d].index)", m_subQueryIndex); - } - } - -private: - JitRangeScanType m_rangeScanType; - int m_subQueryIndex; -}; - -/** @class CreateEndIteratorInstruction */ -class CreateEndIteratorInstruction : public tvm::Instruction { -public: - CreateEndIteratorInstruction(JitIndexScanDirection index_scan_direction, JitRangeBoundMode range_bound_mode, - JitRangeScanType range_scan_type, int subQueryIndex) - : _index_scan_direction(index_scan_direction), - _range_bound_mode(range_bound_mode), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - {} - - ~CreateEndIteratorInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::IndexIterator* iterator = nullptr; - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int include_bound = (_range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - iterator = createEndIterator(exec_context->_jit_context->m_innerIndex, - exec_context->_jit_context->m_innerEndIteratorKey, - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - iterator = createEndIterator(exec_context->_jit_context->m_index, - exec_context->_jit_context->m_endIteratorKey, - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - iterator = createEndIterator(exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index, - exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_endIteratorKey, - forward_scan, - include_bound); - } - return (uint64_t)iterator; - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int include_bound = (_range_bound_mode == JIT_RANGE_BOUND_INCLUDE) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, - "createEndIterator(%%inner_index, %%inner_end_iterator_key, forward_scan=%d, include_bound=%d)", - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, - "createEndIterator(%%index, %%end_iterator_key, forward_scan=%d, include_bound=%d)", - forward_scan, - include_bound); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, - "createEndIterator(%%sub_query[%d].index, %%sub_query[%d].end_iterator_key, forward_scan=%d, " - "include_bound=%d)", - m_subQueryIndex, - m_subQueryIndex, - forward_scan, - include_bound); - } - } - -private: - JitIndexScanDirection _index_scan_direction; - JitRangeBoundMode _range_bound_mode; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class IsScanEndInstruction */ -class IsScanEndInstruction : public tvm::Instruction { -public: - IsScanEndInstruction(JitIndexScanDirection index_scan_direction, tvm::Instruction* begin_itr_inst, - tvm::Instruction* end_itr_inst, JitRangeScanType range_scan_type, int subQueryIndex) - : _index_scan_direction(index_scan_direction), - _begin_itr_inst(begin_itr_inst), - _end_itr_inst(end_itr_inst), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - { - AddSubInstruction(_begin_itr_inst); - AddSubInstruction(_end_itr_inst); - } - - ~IsScanEndInstruction() final - { - _begin_itr_inst = nullptr; - _end_itr_inst = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - uint64_t result = 0; - MOT::IndexIterator* begin_itr = (MOT::IndexIterator*)_begin_itr_inst->Exec(exec_context); - MOT::IndexIterator* end_itr = (MOT::IndexIterator*)_end_itr_inst->Exec(exec_context); - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - result = isScanEnd(exec_context->_jit_context->m_innerIndex, begin_itr, end_itr, forward_scan); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - result = isScanEnd(exec_context->_jit_context->m_index, begin_itr, end_itr, forward_scan); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - result = isScanEnd( - exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index, begin_itr, end_itr, forward_scan); - } - return result; - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "isScanEnd(%%inner_index, %%iterator="); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "isScanEnd(%%index, %%iterator="); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, "isScanEnd(%%sub_query[%d].index, %%iterator=", m_subQueryIndex); - } - _begin_itr_inst->Dump(); - (void)fprintf(stderr, ", %%end_iterator="); - _end_itr_inst->Dump(); - (void)fprintf(stderr, ", forward_scan=%d)", forward_scan); - } - -private: - JitIndexScanDirection _index_scan_direction; - tvm::Instruction* _begin_itr_inst; - tvm::Instruction* _end_itr_inst; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class GetRowFromIteratorInstruction */ -class GetRowFromIteratorInstruction : public tvm::Instruction { -public: - GetRowFromIteratorInstruction(MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, - tvm::Instruction* begin_itr_inst, Instruction* end_itr_inst, JitRangeScanType range_scan_type, - int subQueryIndex) - : _access_mode(access_mode), - _index_scan_direction(index_scan_direction), - _begin_itr_inst(begin_itr_inst), - _end_itr_inst(end_itr_inst), - _range_scan_type(range_scan_type), - m_subQueryIndex(subQueryIndex) - { - AddSubInstruction(_begin_itr_inst); - AddSubInstruction(_end_itr_inst); - } - - ~GetRowFromIteratorInstruction() final - { - _begin_itr_inst = nullptr; - _end_itr_inst = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - MOT::Row* row = nullptr; - MOT::IndexIterator* begin_itr = (MOT::IndexIterator*)_begin_itr_inst->Exec(exec_context); - MOT::IndexIterator* end_itr = (MOT::IndexIterator*)_end_itr_inst->Exec(exec_context); - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - row = getRowFromIterator( - exec_context->_jit_context->m_innerIndex, begin_itr, end_itr, _access_mode, forward_scan); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - row = - getRowFromIterator(exec_context->_jit_context->m_index, begin_itr, end_itr, _access_mode, forward_scan); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - row = getRowFromIterator(exec_context->_jit_context->m_subQueryData[m_subQueryIndex].m_index, - begin_itr, - end_itr, - _access_mode, - forward_scan); - } - return (uint64_t)row; - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - if (_range_scan_type == JIT_RANGE_SCAN_INNER) { - (void)fprintf(stderr, "%%row = getRowFromIterator(%%inner_index, %%iterator="); - } else if (_range_scan_type == JIT_RANGE_SCAN_MAIN) { - (void)fprintf(stderr, "%%row = getRowFromIterator(%%index, %%iterator="); - } else if (_range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - (void)fprintf(stderr, "%%row = getRowFromIterator(%%sub_query[%d].index, %%iterator=", m_subQueryIndex); - } - _begin_itr_inst->Dump(); - (void)fprintf(stderr, ", %%end_iterator="); - _end_itr_inst->Dump(); - (void)fprintf(stderr, ", access_mode=%d, forward_scan=%d)", (int)_access_mode, forward_scan); - } - -private: - MOT::AccessType _access_mode; - JitIndexScanDirection _index_scan_direction; - tvm::Instruction* _begin_itr_inst; - tvm::Instruction* _end_itr_inst; - JitRangeScanType _range_scan_type; - int m_subQueryIndex; -}; - -/** @class DestroyIteratorInstruction */ -class DestroyIteratorInstruction : public tvm::Instruction { -public: - explicit DestroyIteratorInstruction(Instruction* itr_inst) - : Instruction(tvm::Instruction::Void), _itr_inst(itr_inst) - { - AddSubInstruction(_itr_inst); - } - - ~DestroyIteratorInstruction() final - { - _itr_inst = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::IndexIterator* itr = (MOT::IndexIterator*)_itr_inst->Exec(exec_context); - destroyIterator(itr); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "destroyIterator(%%iterator="); - _itr_inst->Dump(); - (void)fprintf(stderr, ")"); - } - -private: - Instruction* _itr_inst; -}; - -class IsNewScanInstruction : public tvm::Instruction { -public: - IsNewScanInstruction() - {} - - ~IsNewScanInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)exec_context->m_newScan; - } - - void DumpImpl() final - { - (void)fprintf(stderr, "isNewScan()"); - } -}; - -/** @class GetStateIteratorInstruction */ -class SetStateIteratorInstruction : public tvm::Instruction { -public: - SetStateIteratorInstruction( - tvm::Instruction* itr, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) - : Instruction(tvm::Instruction::Void), - _itr(itr), - _range_itr_type(range_itr_type), - _range_scan_type(range_scan_type) - { - AddSubInstruction(_itr); - } - - ~SetStateIteratorInstruction() final - { - _itr = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - MOT::IndexIterator* itr = (MOT::IndexIterator*)_itr->Exec(exec_context); - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - setStateIterator(itr, begin_itr, inner_scan); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "setStateIterator("); - _itr->Dump(); - (void)fprintf(stderr, ", begin_itr=%d, inner_scan=%d)", begin_itr, inner_scan); - } - -private: - tvm::Instruction* _itr; - JitRangeIteratorType _range_itr_type; - JitRangeScanType _range_scan_type; -}; - -/** @class GetStateIteratorInstruction */ -class GetStateIteratorInstruction : public tvm::Instruction { -public: - GetStateIteratorInstruction(JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) - : _range_itr_type(range_itr_type), _range_scan_type(range_scan_type) - {} - - ~GetStateIteratorInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)getStateIterator(begin_itr, inner_scan); - } - - void DumpImpl() final - { - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "getStateIterator(begin_itr=%d, inner_scan=%d)", begin_itr, inner_scan); - } - -private: - JitRangeIteratorType _range_itr_type; - JitRangeScanType _range_scan_type; -}; - -/** @class IsStateIteratorNullInstruction */ -class IsStateIteratorNullInstruction : public tvm::Instruction { -public: - IsStateIteratorNullInstruction(JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) - : _range_itr_type(range_itr_type), _range_scan_type(range_scan_type) - {} - - ~IsStateIteratorNullInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)isStateIteratorNull(begin_itr, inner_scan); - } - - void DumpImpl() final - { - int begin_itr = (_range_itr_type == JIT_RANGE_ITERATOR_START) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "isStateIteratorNull(begin_itr=%d, inner_scan=%d)", begin_itr, inner_scan); - } - -private: - JitRangeIteratorType _range_itr_type; - JitRangeScanType _range_scan_type; -}; - -/** @class IsStateScanEndInstruction */ -class IsStateScanEndInstruction : public tvm::Instruction { -public: - IsStateScanEndInstruction(JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) - : _index_scan_direction(index_scan_direction), _range_scan_type(range_scan_type) - {} - - ~IsStateScanEndInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)isStateScanEnd(forward_scan, inner_scan); - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "isStateScanEnd(%%index, forward_scan=%d, inner_scan=%d)", forward_scan, inner_scan); - } - -private: - JitIndexScanDirection _index_scan_direction; - JitRangeScanType _range_scan_type; -}; - -/** @class GetRowFromStateIteratorInstruction */ -class GetRowFromStateIteratorInstruction : public tvm::Instruction { -public: - GetRowFromStateIteratorInstruction( - MOT::AccessType access_mode, JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) - : _access_mode(access_mode), _index_scan_direction(index_scan_direction), _range_scan_type(range_scan_type) - {} - - ~GetRowFromStateIteratorInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - MOT::Row* row = getRowFromStateIterator(_access_mode, forward_scan, inner_scan); - return (uint64_t)row; - } - - void DumpImpl() final - { - int forward_scan = (_index_scan_direction == JIT_INDEX_SCAN_FORWARD) ? 1 : 0; - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, - "%%row = getRowFromStateIterator(access_mode=%d, forward_scan=%d, inner_scan=%d)", - (int)_access_mode, - forward_scan, - inner_scan); - } - -private: - MOT::AccessType _access_mode; - JitIndexScanDirection _index_scan_direction; - JitRangeScanType _range_scan_type; -}; - -/** @class DestroyStateIteratorsInstruction */ -class DestroyStateIteratorsInstruction : public tvm::Instruction { -public: - explicit DestroyStateIteratorsInstruction(JitRangeScanType range_scan_type) - : Instruction(tvm::Instruction::Void), _range_scan_type(range_scan_type) - {} - - ~DestroyStateIteratorsInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - destroyStateIterators(inner_scan); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "destroyStateIterators(inner_scan=%d)", inner_scan); - } - -private: - JitRangeScanType _range_scan_type; -}; - -/** @class SetStateScanEndFlagInstruction */ -class SetStateScanEndFlagInstruction : public tvm::Instruction { -public: - SetStateScanEndFlagInstruction(int scan_ended, JitRangeScanType range_scan_type) - : Instruction(tvm::Instruction::Void), _scan_ended(scan_ended), _range_scan_type(range_scan_type) - {} - - ~SetStateScanEndFlagInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - setStateScanEndFlag(_scan_ended, inner_scan); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "setStateScanEndFlag(scan_ended=%d, inner_scan=%d)", _scan_ended, inner_scan); - } - -private: - int _scan_ended; - JitRangeScanType _range_scan_type; -}; - -/** @class GetStateScanEndFlagInstruction */ -class GetStateScanEndFlagInstruction : public tvm::Instruction { -public: - explicit GetStateScanEndFlagInstruction(JitRangeScanType range_scan_type) : _range_scan_type(range_scan_type) - {} - - ~GetStateScanEndFlagInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)getStateScanEndFlag(inner_scan); - } - - void DumpImpl() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "getStateScanEndFlag(inner_scan=%d)", inner_scan); - } - -private: - JitRangeScanType _range_scan_type; -}; - -class ResetStateRowInstruction : public tvm::Instruction { -public: - explicit ResetStateRowInstruction(JitRangeScanType range_scan_type) - : Instruction(tvm::Instruction::Void), _range_scan_type(range_scan_type) - {} - - ~ResetStateRowInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - resetStateRow(inner_scan); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "resetStateRow(inner_scan=%d)", inner_scan); - } - -private: - JitRangeScanType _range_scan_type; -}; - -class SetStateRowInstruction : public tvm::Instruction { -public: - SetStateRowInstruction(Instruction* row, JitRangeScanType range_scan_type) - : Instruction(tvm::Instruction::Void), _row(row), _range_scan_type(range_scan_type) - { - AddSubInstruction(_row); - } - - ~SetStateRowInstruction() final - { - _row = nullptr; - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - MOT::Row* row = (MOT::Row*)_row->Exec(exec_context); - setStateRow(row, inner_scan); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "setStateRow(row="); - _row->Dump(); - (void)fprintf(stderr, ", inner_scan=%d)", inner_scan); - } - -private: - Instruction* _row; - JitRangeScanType _range_scan_type; -}; - -class GetStateRowInstruction : public tvm::Instruction { -public: - explicit GetStateRowInstruction(JitRangeScanType range_scan_type) : _range_scan_type(range_scan_type) - {} - - ~GetStateRowInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)getStateRow(inner_scan); - } - - void DumpImpl() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "getStateRow(inner_scan=%d)", inner_scan); - } - -private: - JitRangeScanType _range_scan_type; -}; - -class CopyOuterStateRowInstruction : public tvm::Instruction { -public: - CopyOuterStateRowInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~CopyOuterStateRowInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - copyOuterStateRow(); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "copyOuterStateRow()"); - } -}; - -class GetOuterStateRowCopyInstruction : public tvm::Instruction { -public: - GetOuterStateRowCopyInstruction() - {} - - ~GetOuterStateRowCopyInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)getOuterStateRowCopy(); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "getOuterStateRowCopy()"); - } -}; - -class IsStateRowNullInstruction : public tvm::Instruction { -public: - explicit IsStateRowNullInstruction(JitRangeScanType range_scan_type) : _range_scan_type(range_scan_type) - {} - - ~IsStateRowNullInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - return (uint64_t)isStateRowNull(inner_scan); - } - - void DumpImpl() final - { - int inner_scan = (_range_scan_type == JIT_RANGE_SCAN_INNER) ? 1 : 0; - (void)fprintf(stderr, "isStateRowNull(inner_scan=%d)", inner_scan); - } - -private: - JitRangeScanType _range_scan_type; -}; - -/** @class ResetStateLimitCounterInstruction */ -class ResetStateLimitCounterInstruction : public tvm::Instruction { -public: - ResetStateLimitCounterInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ResetStateLimitCounterInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - resetStateLimitCounter(); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "resetStateLimitCounter()"); - } -}; - -/** @class IncrementStateLimitCounterInstruction */ -class IncrementStateLimitCounterInstruction : public tvm::Instruction { -public: - IncrementStateLimitCounterInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~IncrementStateLimitCounterInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - incrementStateLimitCounter(); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "incrementStateLimitCounter()"); - } -}; - -/** @class GetStateLimitCounterInstruction */ -class GetStateLimitCounterInstruction : public tvm::Instruction { -public: - GetStateLimitCounterInstruction() - {} - - ~GetStateLimitCounterInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - uint64_t value = getStateLimitCounter(); - return (uint64_t)value; - } - - void DumpImpl() final - { - (void)fprintf(stderr, "getStateLimitCounter()"); - } -}; - -/** @class PrepareAvgArrayInstruction */ -class PrepareAvgArrayInstruction : public tvm::Instruction { -public: - PrepareAvgArrayInstruction(int element_type, int element_count) - : Instruction(tvm::Instruction::Void), _element_type(element_type), _element_count(element_count) - {} - - ~PrepareAvgArrayInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - prepareAvgArray(_element_type, _element_count); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "prepareAvgArray(element_type=%d, element_count=%d)", _element_type, _element_count); - } - -private: - int _element_type; - int _element_count; -}; - -/** @class LoadAvgArrayExpression */ -class LoadAvgArrayExpression : public tvm::Expression { -public: - LoadAvgArrayExpression() : Expression(tvm::Expression::CannotFail) - {} - - ~LoadAvgArrayExpression() final - {} - - Datum eval(tvm::ExecContext* exec_context) final - { - return (uint64_t)loadAvgArray(); - } - - void dump() final - { - (void)fprintf(stderr, "loadAvgArray()"); - } -}; - -/** @class SaveAvgArrayInstruction */ -class SaveAvgArrayInstruction : public tvm::Instruction { -public: - explicit SaveAvgArrayInstruction(tvm::Expression* avg_array) - : Instruction(tvm::Instruction::Void), _avg_array(avg_array) - {} - - ~SaveAvgArrayInstruction() final - { - if (_avg_array != nullptr) { - delete _avg_array; - } - } - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - Datum avg_array = _avg_array->eval(exec_context); - saveAvgArray(avg_array); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "saveAvgArray(avg_array="); - _avg_array->dump(); - (void)fprintf(stderr, ")"); - } - -private: - tvm::Expression* _avg_array; -}; - -/** @class ComputeAvgFromArrayInstruction */ -class ComputeAvgFromArrayInstruction : public tvm::Instruction { -public: - explicit ComputeAvgFromArrayInstruction(int element_type) : _element_type(element_type) - {} - - ~ComputeAvgFromArrayInstruction() final - {} - -protected: - Datum ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)computeAvgFromArray(_element_type); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "computeAvgFromArray(element_type=%d)", _element_type); - } - -private: - int _element_type; -}; - -/** @class ResetAggValueInstruction */ -class ResetAggValueInstruction : public tvm::Instruction { -public: - explicit ResetAggValueInstruction(int element_type) - : Instruction(tvm::Instruction::Void), _element_type(element_type) - {} - - ~ResetAggValueInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - resetAggValue(_element_type); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "resetAggValue(element_type=%d)", _element_type); - } - -private: - int _element_type; -}; - -/** @class GetAggValueInstruction */ -class GetAggValueExpression : public tvm::Expression { -public: - GetAggValueExpression() : Expression(tvm::Expression::CannotFail) - {} - - ~GetAggValueExpression() final - {} - - Datum eval(tvm::ExecContext* exec_context) final - { - return (uint64_t)getAggValue(); - } - - void dump() final - { - (void)fprintf(stderr, "getAggValue()"); - } -}; - -/** @class SetAggValueInstruction */ -class SetAggValueInstruction : public tvm::Instruction { -public: - explicit SetAggValueInstruction(tvm::Expression* value) : Instruction(tvm::Instruction::Void), _value(value) - {} - - ~SetAggValueInstruction() final - { - if (_value != nullptr) { - delete _value; - } - } - - Datum Exec(tvm::ExecContext* exec_context) final - { - Datum value = (Datum)_value->eval(exec_context); - setAggValue(value); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "setAggValue(value="); - _value->dump(); - (void)fprintf(stderr, ")"); - } - -private: - tvm::Expression* _value; -}; - -/** @class ResetAggMaxMinNullInstruction */ -class ResetAggMaxMinNullInstruction : public tvm::Instruction { -public: - ResetAggMaxMinNullInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~ResetAggMaxMinNullInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - resetAggMaxMinNull(); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "resetAggMaxMinNull()"); - } -}; - -/** @class SetAggMaxMinNotNullInstruction */ -class SetAggMaxMinNotNullInstruction : public tvm::Instruction { -public: - SetAggMaxMinNotNullInstruction() : Instruction(tvm::Instruction::Void) - {} - - ~SetAggMaxMinNotNullInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - setAggMaxMinNotNull(); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "setAggMaxMinNotNull()"); - } -}; - -/** @class GetAggMaxMinIsNullInstruction */ -class GetAggMaxMinIsNullInstruction : public tvm::Instruction { -public: - GetAggMaxMinIsNullInstruction() - {} - - ~GetAggMaxMinIsNullInstruction() final - {} - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - return (uint64_t)getAggMaxMinIsNull(); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "getAggMaxMinIsNull()"); - } -}; - -/** @class PrepareDistinctSetInstruction */ -class PrepareDistinctSetInstruction : public tvm::Instruction { -public: - explicit PrepareDistinctSetInstruction(int element_type) - : Instruction(tvm::Instruction::Void), _element_type(element_type) - {} - - ~PrepareDistinctSetInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - prepareDistinctSet(_element_type); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "prepareDistinctSet(element_type=%d)", _element_type); - } - -private: - int _element_type; -}; - -/** @class InsertDistinctItemInstruction */ -class InsertDistinctItemInstruction : public tvm::Instruction { -public: - InsertDistinctItemInstruction(int element_type, tvm::Expression* value) : _element_type(element_type), _value(value) - {} - - ~InsertDistinctItemInstruction() final - { - _value = nullptr; - } - -protected: - uint64_t ExecImpl(tvm::ExecContext* exec_context) final - { - Datum value = (Datum)_value->eval(exec_context); - return (uint64_t)insertDistinctItem(_element_type, value); - } - - void DumpImpl() final - { - (void)fprintf(stderr, "insertDistinctItem(element_type=%d, ", _element_type); - _value->dump(); - (void)fprintf(stderr, ")"); - } - -private: - int _element_type; - tvm::Expression* _value; -}; - -/** @class DestroyDistinctSetInstruction */ -class DestroyDistinctSetInstruction : public tvm::Instruction { -public: - explicit DestroyDistinctSetInstruction(int element_type) - : Instruction(tvm::Instruction::Void), _element_type(element_type) - {} - - ~DestroyDistinctSetInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - destroyDistinctSet(_element_type); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "destroyDistinctSet(element_type=%d)", _element_type); - } - -private: - int _element_type; -}; - -/** @class ResetTupleDatumInstruction */ -class ResetTupleDatumInstruction : public tvm::Instruction { -public: - ResetTupleDatumInstruction(int tuple_colid, int zero_type) - : Instruction(tvm::Instruction::Void), _tuple_colid(tuple_colid), _zero_type(zero_type) - {} - - ~ResetTupleDatumInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - resetTupleDatum(exec_context->_slot, _tuple_colid, _zero_type); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "resetTupleDatum(%%slot, tuple_colid=%d, zero_type=%d)", _tuple_colid, _zero_type); - } - -private: - int _tuple_colid; - int _zero_type; -}; - -/** @class ReadTupleDatumExpression */ -class ReadTupleDatumExpression : public tvm::Expression { -public: - ReadTupleDatumExpression(int tuple_colid, int arg_pos) - : Expression(tvm::Expression::CannotFail), _tuple_colid(tuple_colid), _arg_pos(arg_pos) - {} - - ~ReadTupleDatumExpression() final - {} - - Datum eval(tvm::ExecContext* exec_context) final - { - exec_context->_expr_rc = MOT_NO_ERROR; - return (uint64_t)readTupleDatum(exec_context->_slot, _tuple_colid, _arg_pos); - } - - void dump() final - { - (void)fprintf(stderr, "readTupleDatum(%%slot, tuple_colid=%d, arg_pos=%d)", _tuple_colid, _arg_pos); - } - -private: - int _tuple_colid; - int _arg_pos; -}; - -/** @class WriteTupleDatumInstruction */ -class WriteTupleDatumInstruction : public tvm::Instruction { -public: - WriteTupleDatumInstruction(int tuple_colid, tvm::Instruction* value) - : Instruction(tvm::Instruction::Void), _tuple_colid(tuple_colid), _value(value) - { - AddSubInstruction(_value); - } - - ~WriteTupleDatumInstruction() final - { - _value = nullptr; - } - - uint64_t Exec(tvm::ExecContext* execContext) final - { - Datum datum_value = (Datum)_value->Exec(execContext); - writeTupleDatum(execContext->_slot, _tuple_colid, datum_value); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "writeTupleDatum(%%slot, tuple_colid=%d, ", _tuple_colid); - _value->Dump(); - (void)fprintf(stderr, ")"); - } - -private: - int _tuple_colid; - tvm::Instruction* _value; -}; - -class SelectSubQueryResultExpression : public tvm::Expression { -public: - explicit SelectSubQueryResultExpression(int subQueryIndex) - : Expression(tvm::Expression::CannotFail), m_subQueryIndex(subQueryIndex) - {} - - ~SelectSubQueryResultExpression() final - {} - - Datum eval(tvm::ExecContext* exec_context) final - { - exec_context->_expr_rc = MOT_NO_ERROR; - return (uint64_t)SelectSubQueryResult(m_subQueryIndex); - } - - void dump() final - { - (void)fprintf(stderr, "SelectSubQueryResult(%%sub_query_index=%d)", m_subQueryIndex); - } - -private: - int m_subQueryIndex; -}; - -class CopyAggregateToSubQueryResultInstruction : public tvm::Instruction { -public: - explicit CopyAggregateToSubQueryResultInstruction(int subQueryIndex) - : Instruction(tvm::Instruction::Void), m_subQueryIndex(subQueryIndex) - {} - - ~CopyAggregateToSubQueryResultInstruction() final - {} - - uint64_t Exec(tvm::ExecContext* exec_context) final - { - CopyAggregateToSubQueryResult(m_subQueryIndex); - return (uint64_t)MOT::RC_OK; - } - - void Dump() final - { - (void)fprintf(stderr, "CopyAggregateToSubQueryResult(%%sub_query_index=%d)", m_subQueryIndex); - } - -private: - int m_subQueryIndex; -}; - -/** @class GetConstAtExpression */ -class GetConstAtExpression : public tvm::Expression { -public: - explicit GetConstAtExpression(int constId, int argPos) - : Expression(tvm::Expression::CanFail), m_constId(constId), m_argPos(argPos) - {} - - ~GetConstAtExpression() final - {} - - Datum eval(tvm::ExecContext* exec_context) final - { - return (uint64_t)GetConstAt(m_constId, m_argPos); - } - - void dump() final - { - (void)fprintf(stderr, "GetConstAt(constId=%d, argPos=%d)", m_constId, m_argPos); - } - -private: - int m_constId; - int m_argPos; -}; - -inline tvm::Instruction* AddIsSoftMemoryLimitReached(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) IsSoftMemoryLimitReachedInstruction()); -} - -inline void AddInitSearchKey(JitTvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) InitSearchKeyInstruction(rangeScanType, subQueryIndex)); -} - -inline tvm::Instruction* AddGetColumnAt( - JitTvmCodeGenContext* ctx, int colid, JitRangeScanType range_scan_type, int subQueryIndex = -1) -{ - return ctx->_builder->addInstruction( - new (std::nothrow) GetColumnAtInstruction(colid, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddSetExprArgIsNull(JitTvmCodeGenContext* ctx, int arg_pos, int isnull) -{ - return ctx->_builder->addInstruction(new (std::nothrow) SetExprArgIsNullInstruction(arg_pos, isnull)); -} - -inline tvm::Instruction* AddGetExprArgIsNull(JitTvmCodeGenContext* ctx, int arg_pos) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetExprArgIsNullInstruction(arg_pos)); -} - -inline tvm::Expression* AddGetDatumParam(JitTvmCodeGenContext* ctx, int paramid, int arg_pos) -{ - return new (std::nothrow) tvm::ParamExpression(paramid, arg_pos); -} - -inline tvm::Expression* AddReadDatumColumn( - JitTvmCodeGenContext* ctx, MOT::Table* table, tvm::Instruction* row_inst, int table_colid, int arg_pos) -{ - return new (std::nothrow) VarExpression(table, row_inst, table_colid, arg_pos); -} - -inline void AddWriteDatumColumn( - JitTvmCodeGenContext* ctx, int table_colid, tvm::Instruction* row_inst, tvm::Instruction* sub_expr) -{ - // make sure we have a column before issuing the call - // we always write to a main table row (whether UPDATE or range UPDATE, so inner_scan value below is false) - tvm::Instruction* column = AddGetColumnAt(ctx, table_colid, JIT_RANGE_SCAN_MAIN); - ctx->_builder->addInstruction(new (std::nothrow) WriteDatumColumnInstruction(row_inst, column, sub_expr)); -} - -inline void AddBuildDatumKey(JitTvmCodeGenContext* ctx, tvm::Instruction* column_inst, int index_colid, - tvm::Instruction* sub_expr, int value_type, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type, - int subQueryIndex = -1) -{ - int offset = -1; - int size = -1; - if (range_scan_type == JIT_RANGE_SCAN_INNER) { - offset = ctx->m_innerTable_info.m_indexColumnOffsets[index_colid]; - size = ctx->m_innerTable_info.m_index->GetLengthKeyFields()[index_colid]; - } else if (range_scan_type == JIT_RANGE_SCAN_MAIN) { - offset = ctx->_table_info.m_indexColumnOffsets[index_colid]; - size = ctx->_table_info.m_index->GetLengthKeyFields()[index_colid]; - } else if (range_scan_type == JIT_RANGE_SCAN_SUB_QUERY) { - offset = ctx->m_subQueryTableInfo[subQueryIndex].m_indexColumnOffsets[index_colid]; - size = ctx->m_subQueryTableInfo[subQueryIndex].m_index->GetLengthKeyFields()[index_colid]; - } - (void)ctx->_builder->addInstruction(new (std::nothrow) BuildDatumKeyInstruction( - column_inst, sub_expr, index_colid, offset, size, value_type, range_itr_type, range_scan_type, subQueryIndex)); -} - -inline void AddSetBit(JitTvmCodeGenContext* ctx, int colid) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetBitInstruction(colid)); -} - -inline void AddResetBitmapSet(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) ResetBitmapSetInstruction()); -} - -inline tvm::Instruction* AddWriteRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row_inst) -{ - return ctx->_builder->addInstruction(new (std::nothrow) WriteRowInstruction(row_inst)); -} - -inline tvm::Instruction* AddSearchRow( - JitTvmCodeGenContext* ctx, MOT::AccessType access_mode_value, JitRangeScanType range_scan_type, int subQueryIndex) -{ - return ctx->_builder->addInstruction( - new (std::nothrow) SearchRowInstruction(access_mode_value, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddCreateNewRow(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) CreateNewRowInstruction()); -} - -inline tvm::Instruction* AddInsertRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row_inst) -{ - return ctx->_builder->addInstruction(new (std::nothrow) InsertRowInstruction(row_inst)); -} - -inline tvm::Instruction* AddDeleteRow(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) DeleteRowInstruction()); -} - -inline void AddSetRowNullBits(JitTvmCodeGenContext* ctx, tvm::Instruction* row_inst) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetRowNullBitsInstruction(row_inst)); -} - -inline tvm::Instruction* AddSetExprResultNullBit(JitTvmCodeGenContext* ctx, tvm::Instruction* row_inst, int colid) -{ - return ctx->_builder->addInstruction(new (std::nothrow) SetExprResultNullBitInstruction(row_inst, colid)); -} - -inline void AddExecClearTuple(JitTvmCodeGenContext* ctx) -{ - ctx->_builder->addInstruction(new (std::nothrow) ExecClearTupleInstruction()); -} - -inline void AddExecStoreVirtualTuple(JitTvmCodeGenContext* ctx) -{ - ctx->_builder->addInstruction(new (std::nothrow) ExecStoreVirtualTupleInstruction()); -} - -inline void AddSelectColumn(JitTvmCodeGenContext* ctx, tvm::Instruction* rowInst, int tableColumnId, int tupleColumnId, - JitRangeScanType rangeScanType, int subQueryIndex) -{ - ctx->_builder->addInstruction(new (std::nothrow) - SelectColumnInstruction(rowInst, tableColumnId, tupleColumnId, rangeScanType, subQueryIndex)); -} - -inline void AddSetTpProcessed(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetTpProcessedInstruction()); -} - -inline void AddSetScanEnded(JitTvmCodeGenContext* ctx, int result) -{ - ctx->_builder->addInstruction(new (std::nothrow) SetScanEndedInstruction(result)); -} - -inline void AddCopyKey(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type, int subQueryIndex) -{ - ctx->_builder->addInstruction(new (std::nothrow) CopyKeyInstruction(range_scan_type, subQueryIndex)); -} - -inline void AddFillKeyPattern(JitTvmCodeGenContext* ctx, unsigned char pattern, int offset, int size, - JitRangeIteratorType rangeItrType, JitRangeScanType rangeScanType, int subQueryIndex) -{ - ctx->_builder->addInstruction(new (std::nothrow) - FillKeyPatternInstruction(rangeItrType, pattern, offset, size, rangeScanType, subQueryIndex)); -} - -inline void AddAdjustKey(JitTvmCodeGenContext* ctx, unsigned char pattern, JitRangeIteratorType range_itr_type, - JitRangeScanType range_scan_type, int subQueryIndex) -{ - (void)ctx->_builder->addInstruction( - new (std::nothrow) AdjustKeyInstruction(range_itr_type, pattern, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddSearchIterator(JitTvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitRangeBoundMode range_bound_mode, JitRangeScanType range_scan_type, int subQueryIndex) -{ - return ctx->_builder->addInstruction(new (std::nothrow) - SearchIteratorInstruction(index_scan_direction, range_bound_mode, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddBeginIterator(JitTvmCodeGenContext* ctx, JitRangeScanType rangeScanType, int subQueryIndex) -{ - return ctx->_builder->addInstruction(new (std::nothrow) BeginIteratorInstruction(rangeScanType, subQueryIndex)); -} - -inline tvm::Instruction* AddCreateEndIterator(JitTvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitRangeBoundMode range_bound_mode, JitRangeScanType range_scan_type, int subQueryIndex = -1) -{ - return ctx->_builder->addInstruction(new (std::nothrow) - CreateEndIteratorInstruction(index_scan_direction, range_bound_mode, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddIsScanEnd(JitTvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, - JitTvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, int subQueryIndex = -1) -{ - return ctx->_builder->addInstruction(new (std::nothrow) IsScanEndInstruction( - index_scan_direction, cursor->begin_itr, cursor->end_itr, range_scan_type, subQueryIndex)); -} - -inline tvm::Instruction* AddGetRowFromIterator(JitTvmCodeGenContext* ctx, MOT::AccessType access_mode, - JitIndexScanDirection index_scan_direction, JitTvmRuntimeCursor* cursor, JitRangeScanType range_scan_type, - int subQueryIndex) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetRowFromIteratorInstruction( - access_mode, index_scan_direction, cursor->begin_itr, cursor->end_itr, range_scan_type, subQueryIndex)); -} - -inline void AddDestroyIterator(JitTvmCodeGenContext* ctx, tvm::Instruction* itr_inst) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) DestroyIteratorInstruction(itr_inst)); -} - -inline void AddDestroyCursor(JitTvmCodeGenContext* ctx, JitTvmRuntimeCursor* cursor) -{ - if (cursor != nullptr) { - AddDestroyIterator(ctx, cursor->begin_itr); - AddDestroyIterator(ctx, cursor->end_itr); - } -} - -/** @brief Adds a call to pseudo-function isNewScan(begin_itr). */ -inline tvm::Instruction* AddIsNewScan(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) IsNewScanInstruction()); -} - -/** @brief Adds a call to setStateIterator(itr, begin_itr). */ -inline void AddSetStateIterator(JitTvmCodeGenContext* ctx, tvm::Instruction* itr, JitRangeIteratorType range_itr_type, - JitRangeScanType range_scan_type) -{ - (void)ctx->_builder->addInstruction( - new (std::nothrow) SetStateIteratorInstruction(itr, range_itr_type, range_scan_type)); -} - -/** @brief Adds a call to isStateIteratorNull(begin_itr). */ -inline tvm::Instruction* AddIsStateIteratorNull( - JitTvmCodeGenContext* ctx, JitRangeIteratorType range_itr_type, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction( - new (std::nothrow) IsStateIteratorNullInstruction(range_itr_type, range_scan_type)); -} - -/** @brief Adds a call to isStateScanEnd(index, forward_scan). */ -inline tvm::Instruction* AddIsStateScanEnd( - JitTvmCodeGenContext* ctx, JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction( - new (std::nothrow) IsStateScanEndInstruction(index_scan_direction, range_scan_type)); -} - -inline tvm::Instruction* AddGetRowFromStateIterator(JitTvmCodeGenContext* ctx, MOT::AccessType access_mode, - JitIndexScanDirection index_scan_direction, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction( - new (std::nothrow) GetRowFromStateIteratorInstruction(access_mode, index_scan_direction, range_scan_type)); -} - -/** @brief Adds a call to setStateScanEndFlag(scan_ended). */ -inline void AddSetStateScanEndFlag(JitTvmCodeGenContext* ctx, int scan_ended, JitRangeScanType range_scan_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetStateScanEndFlagInstruction(scan_ended, range_scan_type)); -} - -/** @brief Adds a call to getStateScanEndFlag(). */ -inline tvm::Instruction* AddGetStateScanEndFlag(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetStateScanEndFlagInstruction(range_scan_type)); -} - -inline void AddResetStateRow(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) ResetStateRowInstruction(range_scan_type)); -} - -inline void AddSetStateRow(JitTvmCodeGenContext* ctx, tvm::Instruction* row, JitRangeScanType range_scan_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetStateRowInstruction(row, range_scan_type)); -} - -inline tvm::Instruction* AddGetStateRow(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetStateRowInstruction(range_scan_type)); -} - -inline void AddCopyOuterStateRow(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) CopyOuterStateRowInstruction()); -} - -inline tvm::Instruction* AddGetOuterStateRowCopy(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetOuterStateRowCopyInstruction()); -} - -inline tvm::Instruction* AddIsStateRowNull(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type) -{ - return ctx->_builder->addInstruction(new (std::nothrow) IsStateRowNullInstruction(range_scan_type)); -} - -/** @brief Adds a call to destroyStateIterators(). */ -inline void AddDestroyStateIterators(JitTvmCodeGenContext* ctx, JitRangeScanType range_scan_type) -{ - ctx->_builder->addInstruction(new (std::nothrow) DestroyStateIteratorsInstruction(range_scan_type)); -} - -inline void AddResetStateLimitCounter(JitTvmCodeGenContext* ctx) -{ - ctx->_builder->addInstruction(new (std::nothrow) ResetStateLimitCounterInstruction()); -} - -inline void AddIncrementStateLimitCounter(JitTvmCodeGenContext* ctx) -{ - ctx->_builder->addInstruction(new (std::nothrow) IncrementStateLimitCounterInstruction()); -} - -inline tvm::Instruction* AddGetStateLimitCounter(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetStateLimitCounterInstruction()); -} - -inline void AddPrepareAvgArray(JitTvmCodeGenContext* ctx, int element_type, int element_count) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) PrepareAvgArrayInstruction(element_type, element_count)); -} - -inline tvm::Expression* AddLoadAvgArray(JitTvmCodeGenContext* ctx) -{ - return new (std::nothrow) LoadAvgArrayExpression(); -} - -inline void AddSaveAvgArray(JitTvmCodeGenContext* ctx, tvm::Expression* avg_array) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SaveAvgArrayInstruction(avg_array)); -} - -inline tvm::Instruction* AddComputeAvgFromArray(JitTvmCodeGenContext* ctx, int element_type) -{ - return ctx->_builder->addInstruction(new (std::nothrow) ComputeAvgFromArrayInstruction(element_type)); -} - -inline void AddResetAggValue(JitTvmCodeGenContext* ctx, int element_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) ResetAggValueInstruction(element_type)); -} - -inline tvm::Expression* AddGetAggValue(JitTvmCodeGenContext* ctx) -{ - return new (std::nothrow) GetAggValueExpression(); -} - -inline void AddSetAggValue(JitTvmCodeGenContext* ctx, tvm::Expression* value) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetAggValueInstruction(value)); -} - -inline void AddResetAggMaxMinNull(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) ResetAggMaxMinNullInstruction()); -} - -inline void AddSetAggMaxMinNotNull(JitTvmCodeGenContext* ctx) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) SetAggMaxMinNotNullInstruction()); -} - -inline tvm::Instruction* AddGetAggMaxMinIsNull(JitTvmCodeGenContext* ctx) -{ - return ctx->_builder->addInstruction(new (std::nothrow) GetAggMaxMinIsNullInstruction()); -} - -inline void AddPrepareDistinctSet(JitTvmCodeGenContext* ctx, int element_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) PrepareDistinctSetInstruction(element_type)); -} - -inline tvm::Instruction* AddInsertDistinctItem(JitTvmCodeGenContext* ctx, int element_type, tvm::Expression* value) -{ - return ctx->_builder->addInstruction(new (std::nothrow) InsertDistinctItemInstruction(element_type, value)); -} - -inline void AddDestroyDistinctSet(JitTvmCodeGenContext* ctx, int element_type) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) DestroyDistinctSetInstruction(element_type)); -} - -inline void AddWriteTupleDatum(JitTvmCodeGenContext* ctx, int tuple_colid, tvm::Instruction* datum_value) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) WriteTupleDatumInstruction(tuple_colid, datum_value)); -} - -inline tvm::Expression* AddSelectSubQueryResult(JitTvmCodeGenContext* ctx, int subQueryIndex) -{ - return new (std::nothrow) SelectSubQueryResultExpression(subQueryIndex); -} - -inline void AddCopyAggregateToSubQueryResult(JitTvmCodeGenContext* ctx, int subQueryIndex) -{ - ctx->_builder->addInstruction(new (std::nothrow) CopyAggregateToSubQueryResultInstruction(subQueryIndex)); -} - -inline tvm::Expression* AddGetConstAt(JitTvmCodeGenContext* ctx, int constId, int argPos) -{ - return new (std::nothrow) GetConstAtExpression(constId, argPos); -} - -#ifdef MOT_JIT_DEBUG -inline void IssueDebugLogImpl(JitTvmCodeGenContext* ctx, const char* function, const char* msg) -{ - (void)ctx->_builder->addInstruction(new (std::nothrow) tvm::DebugLogInstruction(function, msg)); -} -#endif - -#ifdef MOT_JIT_DEBUG -#define IssueDebugLog(msg) IssueDebugLogImpl(ctx, __func__, msg) -#else -#define IssueDebugLog(msg) -#endif -} // namespace JitExec - -#endif /* JIT_TVM_FUNCS_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm_query.h deleted file mode 100644 index f9d17567d..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_query.h - * TVM-jitted query codegen common header. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_query.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_QUERY_H -#define JIT_TVM_QUERY_H - -/* - * ATTENTION: Be sure to include libintl.h before anything else to avoid problem with gettext. - */ -#include "libintl.h" -#include "jit_common.h" -#include "jit_tvm.h" - -namespace JitExec { -/** @struct Holds instructions that evaluate in runtime to begin and end iterators of a cursor. */ -struct JitTvmRuntimeCursor { - /** @var The iterator pointing to the beginning of the range. */ - tvm::Instruction* begin_itr; - - /** @var The iterator pointing to the end of the range. */ - tvm::Instruction* end_itr; -}; - -/** @struct Context used for compiling tvm-jitted functions. */ -struct JitTvmCodeGenContext { - /** @var Main table info. */ - TableInfo _table_info; - - /** @var Inner table info (in JOIN queries). */ - TableInfo m_innerTable_info; - - /** @var Sub-query table info (in COMPOUND queries). */ - TableInfo* m_subQueryTableInfo; - - /** @var Sub-query count (in COMPOUND queries). */ - uint64_t m_subQueryCount; - - /** @var The builder used for emitting code. */ - tvm::Builder* _builder; - - /** @var The resulting jitted function. */ - tvm::Function* m_jittedQuery; - - // non-primitive constants - uint32_t m_constCount; - Const* m_constValues; -}; - -extern int AllocateConstId(JitTvmCodeGenContext* ctx, int type, Datum value, bool isNull); -} // namespace JitExec - -#endif /* JIT_TVM_QUERY_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.cpp b/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.cpp deleted file mode 100644 index 974cab4c5..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.cpp +++ /dev/null @@ -1,1784 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_query_codegen.cpp - * TVM-jitted code generation. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.cpp - * - * ------------------------------------------------------------------------- - */ - -/* - * ATTENTION: Be sure to include jit_tvm_query.h before anything else because of libintl.h - * See jit_tvm_query.h for more details. - */ -#include "jit_tvm_query.h" -#include "jit_tvm_query_codegen.h" -#include "jit_tvm_funcs.h" -#include "jit_tvm_blocks.h" -#include "storage/mot/jit_exec.h" -#include "jit_tvm_util.h" -#include "jit_util.h" - -using namespace tvm; - -namespace JitExec { -DECLARE_LOGGER(JitTvmQueryCodegen, JitExec) - -static void DestroyCodeGenContext(JitTvmCodeGenContext* ctx); - -/** @brief Initializes a context for compilation. */ -static bool InitCodeGenContext(JitTvmCodeGenContext* ctx, Builder* builder, MOT::Table* table, MOT::Index* index, - MOT::Table* inner_table = nullptr, MOT::Index* inner_index = nullptr) -{ - errno_t erc = memset_s(ctx, sizeof(JitTvmCodeGenContext), 0, sizeof(JitTvmCodeGenContext)); - securec_check(erc, "\0", "\0"); - ctx->_builder = builder; - if (!InitTableInfo(&ctx->_table_info, table, index)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "JIT Compile", "Failed to initialize table information for code-generation context"); - return false; - } - if (inner_table && !InitTableInfo(&ctx->m_innerTable_info, inner_table, inner_index)) { - DestroyTableInfo(&ctx->_table_info); - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "JIT Compile", - "Failed to initialize inner-scan table information for code-generation context"); - return false; - } - - ctx->m_constCount = 0; - size_t allocSize = sizeof(Const) * MOT_JIT_MAX_CONST; - ctx->m_constValues = (Const*)MOT::MemSessionAlloc(allocSize); - if (ctx->m_constValues == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "JIT Compile", - "Failed to allocate %u bytes for constant array in code-generation context", - allocSize); - DestroyCodeGenContext(ctx); - return false; - } - - return true; -} - -/** @brief Initializes a context for compilation. */ -static bool InitCompoundCodeGenContext( - JitTvmCodeGenContext* ctx, Builder* builder, MOT::Table* table, MOT::Index* index, JitCompoundPlan* plan) -{ - // initialize outer query table info - errno_t erc = memset_s(ctx, sizeof(JitTvmCodeGenContext), 0, sizeof(JitTvmCodeGenContext)); - securec_check(erc, "\0", "\0"); - ctx->_builder = builder; - if (!InitTableInfo(&ctx->_table_info, table, index)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "JIT Compile", "Failed to initialize table information for code-generation context"); - return false; - } - ctx->m_subQueryCount = plan->_sub_query_count; - ctx->m_subQueryTableInfo = (TableInfo*)MOT::MemSessionAlloc(sizeof(TableInfo) * ctx->m_subQueryCount); - if (ctx->m_subQueryTableInfo == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "JIT Compile", "Failed to initialize table information for code-generation context"); - DestroyTableInfo(&ctx->_table_info); - return false; - } - - // initialize sub-query table info - bool result = true; - for (uint32_t i = 0; i < ctx->m_subQueryCount; ++i) { - JitPlan* subPlan = plan->_sub_query_plans[i]; - MOT::Table* subTable = nullptr; - MOT::Index* subIndex = nullptr; - if (subPlan->_plan_type == JIT_PLAN_POINT_QUERY) { - subTable = ((JitSelectPlan*)subPlan)->_query._table; - subIndex = subTable->GetPrimaryIndex(); - } else if (subPlan->_plan_type == JIT_PLAN_RANGE_SCAN) { - subTable = ((JitRangeSelectPlan*)subPlan)->_index_scan._table; - subIndex = subTable->GetIndex(((JitRangeSelectPlan*)subPlan)->_index_scan._index_id); - } else { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "JIT Compile", "Invalid sub-plan %u type: %d", i, (int)subPlan->_plan_type); - result = false; - } - if (result && !InitTableInfo(&ctx->m_subQueryTableInfo[i], subTable, subIndex)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "JIT Compile", - "Failed to initialize sub-query table information for code-generation context"); - result = false; - } - if (!result) { - for (uint32_t j = 0; j < i; ++j) { - DestroyTableInfo(&ctx->m_subQueryTableInfo[j]); - } - MOT::MemSessionFree(ctx->m_subQueryTableInfo); - ctx->m_subQueryTableInfo = nullptr; - DestroyTableInfo(&ctx->_table_info); - return false; - } - } - - return true; -} - -/** @brief Destroys a compilation context. */ -static void DestroyCodeGenContext(JitTvmCodeGenContext* ctx) -{ - if (ctx != nullptr) { - DestroyTableInfo(&ctx->_table_info); - DestroyTableInfo(&ctx->m_innerTable_info); - for (uint32_t i = 0; i < ctx->m_subQueryCount; ++i) { - DestroyTableInfo(&ctx->m_subQueryTableInfo[i]); - } - if (ctx->m_constValues != nullptr) { - MOT::MemSessionFree(ctx->m_constValues); - } - } -} - -extern int AllocateConstId(JitTvmCodeGenContext* ctx, int type, Datum value, bool isNull) -{ - int res = -1; - if (ctx->m_constCount == MOT_JIT_MAX_CONST) { - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, - "JIT Compile", - "Cannot allocate constant identifier, reached limit of %u", - ctx->m_constCount); - } else { - res = ctx->m_constCount++; - ctx->m_constValues[res].consttype = type; - ctx->m_constValues[res].constvalue = value; - ctx->m_constValues[res].constisnull = isNull; - } - return res; -} - -static JitContext* FinalizeCodegen(JitTvmCodeGenContext* ctx, int max_arg, JitCommandType command_type) -{ - // do minimal verification and wrap up - if (!ctx->m_jittedQuery->finalize()) { - MOT_LOG_ERROR("Failed to generate jitted code for query: Failed to finalize jit function"); - ctx->m_jittedQuery->dump(); - delete ctx->m_jittedQuery; - return nullptr; - } - - // dump if requested - if (IsMotCodegenPrintEnabled()) { - ctx->m_jittedQuery->dump(); - } - - // verify function structure - if (!ctx->m_jittedQuery->verify()) { - MOT_LOG_TRACE("Failed to generate jitted code for query: Failed to verify jit function"); - delete ctx->m_jittedQuery; - return nullptr; - } - - // prepare global constant array - JitDatumArray datumArray = {}; - if (ctx->m_constCount > 0) { - if (!PrepareDatumArray(ctx->m_constValues, ctx->m_constCount, &datumArray)) { - MOT_LOG_ERROR("Failed to generate jitted code for query: Failed to prepare constant datum array"); - delete ctx->m_jittedQuery; - return nullptr; - } - } - - // that's it, we are ready - JitContext* jit_context = AllocJitContext(JIT_CONTEXT_GLOBAL); - if (jit_context == nullptr) { - MOT_LOG_TRACE("Failed to allocate JIT context, aborting code generation"); - delete ctx->m_jittedQuery; - return nullptr; - } - - // setup execution details - jit_context->m_table = ctx->_table_info.m_table; - jit_context->m_index = ctx->_table_info.m_index; - jit_context->m_indexId = jit_context->m_index->GetExtId(); - MOT_LOG_TRACE("Installed index id: %" PRIu64, jit_context->m_indexId); - jit_context->m_tvmFunction = ctx->m_jittedQuery; - jit_context->m_argCount = max_arg + 1; - jit_context->m_innerTable = ctx->m_innerTable_info.m_table; - jit_context->m_innerIndex = ctx->m_innerTable_info.m_index; - if (jit_context->m_innerIndex != nullptr) { - jit_context->m_innerIndexId = jit_context->m_innerIndex->GetExtId(); - MOT_LOG_TRACE("Installed inner index id: %" PRIu64, jit_context->m_innerIndexId); - } - jit_context->m_commandType = command_type; - jit_context->m_subQueryCount = 0; - jit_context->m_constDatums.m_datumCount = datumArray.m_datumCount; - jit_context->m_constDatums.m_datums = datumArray.m_datums; - - return jit_context; -} - -static JitContext* JitUpdateCodegen(const Query* query, const char* query_string, JitUpdatePlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT update at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedUpdate", query_string); - IssueDebugLog("Starting execution of jitted UPDATE"); - - // update is not allowed if we reached soft memory limit - buildIsSoftMemoryLimitReached(ctx); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // begin the WHERE clause (this is a point query - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // fetch row for writing - MOT_LOG_DEBUG("Generating update code for point query"); - Instruction* row = buildSearchRow(ctx, MOT::AccessType::WR, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // prepare a bitmap array - IssueDebugLog("Resetting bitmap set for incremental redo"); - AddResetBitmapSet(ctx); - - // now begin updating columns - IssueDebugLog("Updating row columns"); - if (!writeRowColumns(ctx, row, &plan->_update_exprs, &max_arg, true)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: failed to process target entry"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // write row - IssueDebugLog("Writing row"); - buildWriteRow(ctx, row, true, nullptr); - - // the next call will be executed only if the previous call to writeRow succeeded - buildIncrementRowsProcessed(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_UPDATE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitRangeUpdateCodegen(const Query* query, const char* query_string, JitRangeUpdatePlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT range update at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_index_scan._table; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedRangeUpdate", query_string); - IssueDebugLog("Starting execution of jitted range UPDATE"); - - // update is not allowed if we reached soft memory limit - buildIsSoftMemoryLimitReached(ctx); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // begin the WHERE clause - int max_arg = 0; - MOT_LOG_DEBUG("Generating range cursor for range UPDATE query"); - JitTvmRuntimeCursor cursor = - buildRangeCursor(ctx, &plan->_index_scan, &max_arg, JIT_RANGE_SCAN_MAIN, JIT_INDEX_SCAN_FORWARD, nullptr); - if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for range UPDATE query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddResetBitmapSet(ctx); - - JIT_WHILE_BEGIN(cursor_loop) - Instruction* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - Instruction* row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), MOT::AccessType::WR, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_index_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for range UPDATE query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // now begin updating columns - IssueDebugLog("Updating row columns"); - if (!writeRowColumns(ctx, row, &plan->_update_exprs, &max_arg, true)) { - MOT_LOG_TRACE("Failed to generate jitted code for update query: failed to process target entry"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // write row - IssueDebugLog("Writing row"); - buildWriteRow(ctx, row, false, &cursor); - - // the next call will be executed only if the previous call to writeRow succeeded - buildIncrementRowsProcessed(ctx); - - // reset bitmap for next loop - AddResetBitmapSet(ctx); - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of range update loop"); - AddDestroyCursor(ctx, &cursor); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_UPDATE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitInsertCodegen(const Query* query, const char* query_string, JitInsertPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT insert at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_table; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedInsert", query_string); - IssueDebugLog("Starting execution of jitted INSERT"); - - // insert is not allowed if we reached soft memory limit - buildIsSoftMemoryLimitReached(ctx); - - // create new row and bitmap set - Instruction* row = buildCreateNewRow(ctx); - - // set row null bits - IssueDebugLog("Setting row null bits before insert"); - AddSetRowNullBits(ctx, row); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - IssueDebugLog("Setting row columns"); - int max_arg = 0; - if (!writeRowColumns(ctx, row, &plan->_insert_exprs, &max_arg, false)) { - MOT_LOG_TRACE("Failed to generate jitted code for insert query: failed to process target entry"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - IssueDebugLog("Inserting row"); - buildInsertRow(ctx, row); - - // the next call will be executed only if the previous call to writeRow succeeded - buildIncrementRowsProcessed(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_INSERT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitDeleteCodegen(const Query* query, const char* query_string, JitDeletePlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT delete at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedDelete", query_string); - IssueDebugLog("Starting execution of jitted DELETE"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for DELETE query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // fetch row for delete - Instruction* row = buildSearchRow(ctx, MOT::AccessType::DEL, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for DELETE query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // delete row - IssueDebugLog("Deleting row"); - buildDeleteRow( - ctx); // row is already cached in concurrency control module, so we do not need to provide an argument - - // the next call will be executed only if the previous call to deleteRow succeeded - buildIncrementRowsProcessed(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_DELETE); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitSelectCodegen(const Query* query, const char* query_string, JitSelectPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT select at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_query._table; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex())) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedSelect", query_string); - IssueDebugLog("Starting execution of jitted SELECT"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // fetch row for read - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // now begin selecting columns into result - IssueDebugLog("Selecting columns into result"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for insert query: failed to process target entry"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - - // update number of rows processed - buildIncrementRowsProcessed(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended (this is a point query) - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_SELECT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static void AddCleanupOldScan(JitTvmCodeGenContext* ctx) -{ - // emit code to cleanup previous scan in case this is a new scan - JIT_IF_BEGIN(cleanup_old_scan) - Instruction* isNewScan = AddIsNewScan(ctx); - JIT_IF_EVAL(isNewScan) - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_MAIN); - AddDestroyStateIterators(ctx, JIT_RANGE_SCAN_INNER); - // sub-query does not have a stateful execution, so no need to cleanup - JIT_IF_END() -} - -/** @brief Generates code for range SELECT query with a possible LIMIT clause. */ -static JitContext* JitRangeSelectCodegen(const Query* query, const char* query_string, JitRangeSelectPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT select at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_index_scan._table; - int index_id = plan->_index_scan._index_id; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetIndex(index_id))) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedRangeSelect", query_string); - IssueDebugLog("Starting execution of jitted range SELECT"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // emit code to cleanup previous scan in case this is a new scan - AddCleanupOldScan(ctx); - - // prepare stateful scan if not done so already, if no row exists then emit code to return from function - int max_arg = 0; - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* row = buildPrepareStateScanRow( - ctx, &plan->_index_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, nullptr); - if (row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for range select query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // select inner and outer row expressions into result tuple (no aggregate because aggregate is not stateful) - IssueDebugLog("Retrieved row from state iterator, beginning to select columns into result tuple"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for range SELECT query: failed to select row expressions"); - DestroyCodeGenContext(ctx); - return nullptr; - } - AddExecStoreVirtualTuple(ctx); - buildIncrementRowsProcessed(ctx); - - // make sure that next iteration find an empty state row, so scan makes a progress - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); - - // if a limit clause exists, then increment limit counter and check if reached limit - buildCheckLimit(ctx, plan->_limit_count); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitCommandType cmdType = - (plan->_index_scan._scan_type == JIT_INDEX_SCAN_FULL) ? JIT_COMMAND_FULL_SELECT : JIT_COMMAND_RANGE_SELECT; - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, cmdType); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -/** @brief Generates code for range SELECT query with aggregator. */ -static JitContext* JitAggregateRangeSelectCodegen( - const Query* query, const char* query_string, JitRangeSelectPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT aggregate range select at thread %p", (void*)pthread_self()); - - Builder builder; - MOT::Table* table = plan->_index_scan._table; - int index_id = plan->_index_scan._index_id; - JitTvmCodeGenContext cg_ctx = {0}; - if (!InitCodeGenContext(&cg_ctx, &builder, table, table->GetIndex(index_id))) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedAggregateRangeSelect", query_string); - IssueDebugLog("Starting execution of jitted aggregate range SELECT"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - AddExecClearTuple(ctx); - - // prepare for aggregation - if (!prepareAggregate(ctx, &plan->_aggregate)) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: failed to prepare aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddResetStateLimitCounter(ctx); - - // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - - // begin the WHERE clause - int max_arg = 0; - JitIndexScanDirection index_scan_direction = JIT_INDEX_SCAN_FORWARD; - - // build range iterators - MOT_LOG_DEBUG("Generating range cursor for range SELECT query"); - JitTvmRuntimeCursor cursor = - buildRangeCursor(ctx, &plan->_index_scan, &max_arg, JIT_RANGE_SCAN_MAIN, index_scan_direction, nullptr); - if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - JIT_WHILE_BEGIN(cursor_aggregate_loop) - Instruction* res = AddIsScanEnd(ctx, index_scan_direction, &cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - Instruction* row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), access_mode, JIT_INDEX_SCAN_FORWARD, &cursor, JIT_RANGE_SCAN_MAIN); - - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, row, &plan->_index_scan._filters, &max_arg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // if row disqualified due to DISTINCT operator then go back to loop test block - if (!buildAggregateRow(ctx, &plan->_aggregate, row, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT query: unsupported aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // if a limit clause exists, then increment limit counter and check if reached limit - if (plan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - Instruction* current_limit_count = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(current_limit_count, JIT_CONST(plan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - JIT_WHILE_BREAK() // break from loop - JIT_IF_END() - } - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of aggregate range select loop"); - AddDestroyCursor(ctx, &cursor); - - // wrap up aggregation and write to result tuple - buildAggregateResult(ctx, &plan->_aggregate); - - // store the result tuple - AddExecStoreVirtualTuple(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended (this is an aggregate loop) - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_AGGREGATE_RANGE_SELECT); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitPointJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT Point JOIN query at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedPointJoin", query_string); - IssueDebugLog("Starting execution of jitted Point JOIN"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // search the outer row - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // fetch row for read - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported outer scan filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner point scan, we save the outer row in a safe copy (but for that purpose we need to save - // row in outer scan state) - AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); - AddCopyOuterStateRow(ctx); - - // now search the inner row - if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_INNER, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - Instruction* inner_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); - - // check for additional filters - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Point JOIN query: unsupported inner scan filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // retrieve the safe copy of the outer row - Instruction* outer_row_copy = AddGetOuterStateRowCopy(ctx); - - // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Point JOIN query: failed to select row columns into result tuple"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - - // update number of rows processed - buildIncrementRowsProcessed(ctx); - - // generate code for setting output parameter tp_processed value to rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended (this is a point query) - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_POINT_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -/** @brief Generates code for range JOIN query with outer point query and a possible LIMIT clause but without - * aggregation. */ -static JitContext* JitPointOuterJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT Outer Point JOIN query at thread %p", (void*)pthread_self()); - - int max_arg = 0; - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedOuterPointJoin", query_string); - IssueDebugLog("Starting execution of jitted Outer Point JOIN"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // emit code to cleanup previous scan in case this is a new scan - AddCleanupOldScan(ctx); - - // we first check if outer state row was already searched - Instruction* outer_row_copy = AddGetOuterStateRowCopy(ctx); - JIT_IF_BEGIN(check_outer_row_ready) - JIT_IF_EVAL_NOT(outer_row_copy) - // search the outer row - if (!buildPointScan(ctx, &plan->_outer_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // fetch row for read - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* outer_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner range scan, we save the outer row in a safe copy (but for that purpose we need to save - // row in outer scan state) - AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); - AddCopyOuterStateRow(ctx); - outer_row_copy = AddGetOuterStateRowCopy(ctx); // must get copy again, otherwise it is null - JIT_IF_END() - - // now prepare inner scan if needed, if no row was found then emit code to return from function (since outer scan is - // a point query) - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* inner_row = buildPrepareStateScanRow( - ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, &max_arg, outer_row_copy, nullptr, nullptr); - if (inner_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // retrieve the safe copy of the outer row - outer_row_copy = AddGetOuterStateRowCopy(ctx); - - // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE("Failed to generate jitted code for Outer Point JOIN query: failed to select row columns into " - "result tuple"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - - // update number of rows processed - buildIncrementRowsProcessed(ctx); - - // clear inner row for next iteration - AddResetStateRow(ctx, JIT_RANGE_SCAN_INNER); - - // if a limit clause exists, then increment limit counter and check if reached limit - buildCheckLimit(ctx, plan->_limit_count); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -/** @brief Generates code for range JOIN query with inner point query and a possible LIMIT clause but without - * aggregation. */ -static JitContext* JitPointInnerJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT Inner Point JOIN at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedInnerPointJoin", query_string); - IssueDebugLog("Starting execution of jitted inner point JOIN"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // emit code to cleanup previous scan in case this is a new scan - AddCleanupOldScan(ctx); - - // prepare stateful scan if not done so already, if row not found then emit code to return from function (since this - // is an outer scan) - int max_arg = 0; - BasicBlock* fetch_outer_row_bb = nullptr; - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* outer_row = buildPrepareStateScanRow( - ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, &fetch_outer_row_bb); - if (outer_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner scan, we save the outer row in a safe copy - AddCopyOuterStateRow(ctx); - - // now search the inner row - if (!buildPointScan(ctx, &plan->_inner_scan._search_exprs, &max_arg, JIT_RANGE_SCAN_INNER, outer_row)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - Instruction* inner_row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_INNER); - - // check for additional filters - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &max_arg, fetch_outer_row_bb)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: unsupported inner scan filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // retrieve the safe copy of the outer row - Instruction* outer_row_copy = AddGetOuterStateRowCopy(ctx); - - // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE("Failed to generate jitted code for Inner Point JOIN query: failed to select row columns into " - "result tuple"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - buildIncrementRowsProcessed(ctx); - - // make sure that next iteration find an empty state row, so scan makes a progress - AddResetStateRow(ctx, JIT_RANGE_SCAN_MAIN); - - // if a limit clause exists, then increment limit counter and check if reached limit - buildCheckLimit(ctx, plan->_limit_count); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -/** @brief Generates code for range JOIN query with a possible LIMIT clause but without aggregation. */ -static JitContext* JitRangeJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT range JOIN at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedRangeJoin", query_string); - IssueDebugLog("Starting execution of jitted range JOIN"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple even if row is not found later - AddExecClearTuple(ctx); - - // emit code to cleanup previous scan in case this is a new scan - AddCleanupOldScan(ctx); - - // prepare stateful scan if not done so already - int max_arg = 0; - BasicBlock* fetch_outer_row_bb = nullptr; - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* outer_row = buildPrepareStateScanRow( - ctx, &plan->_outer_scan, JIT_RANGE_SCAN_MAIN, access_mode, &max_arg, nullptr, nullptr, &fetch_outer_row_bb); - if (outer_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Range JOIN query: unsupported outer WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner scan, we save the outer row in a safe copy - AddCopyOuterStateRow(ctx); - - // now prepare inner scan if needed - Instruction* inner_row = buildPrepareStateScanRow( - ctx, &plan->_inner_scan, JIT_RANGE_SCAN_INNER, access_mode, &max_arg, outer_row, fetch_outer_row_bb, nullptr); - if (inner_row == nullptr) { - MOT_LOG_TRACE("Failed to generate jitted code for Range JOIN query: unsupported inner WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // retrieve the safe copy of the outer row - Instruction* outer_row_copy = AddGetOuterStateRowCopy(ctx); - - // now begin selecting columns into result - if (!selectJoinRows(ctx, outer_row_copy, inner_row, plan, &max_arg)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for Range JOIN query: failed to select row columns into result tuple"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - buildIncrementRowsProcessed(ctx); - - // clear inner row for next iteration - AddResetStateRow(ctx, JIT_RANGE_SCAN_INNER); - - // if a limit clause exists, then increment limit counter and check if reached limit - buildCheckLimit(ctx, plan->_limit_count); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, max_arg, JIT_COMMAND_RANGE_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -/** @brief Generates code for range JOIN query with an aggregator. */ -static JitContext* JitAggregateRangeJoinCodegen(const Query* query, const char* query_string, JitJoinPlan* plan) -{ - MOT_LOG_DEBUG("Generating code for MOT aggregate range JOIN at thread %p", (void*)pthread_self()); - - Builder builder; - - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* outer_table = plan->_outer_scan._table; - MOT::Index* outer_index = outer_table->GetIndex(plan->_outer_scan._index_id); - MOT::Table* inner_table = plan->_inner_scan._table; - MOT::Index* inner_index = inner_table->GetIndex(plan->_inner_scan._index_id); - if (!InitCodeGenContext(&cg_ctx, &builder, outer_table, outer_index, inner_table, inner_index)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedAggregateRangeJoin", query_string); - IssueDebugLog("Starting execution of jitted aggregate range JOIN"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // clear tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - AddExecClearTuple(ctx); - - // prepare for aggregation - if (!prepareAggregate(ctx, &plan->_aggregate)) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: failed to prepare aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - AddResetStateLimitCounter(ctx); - - // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - - // begin the WHERE clause - int maxArg = 0; - - // build range iterators - MOT_LOG_DEBUG("Generating outer loop cursor for range JOIN query"); - JitTvmRuntimeCursor outer_cursor = - buildRangeCursor(ctx, &plan->_outer_scan, &maxArg, JIT_RANGE_SCAN_MAIN, JIT_INDEX_SCAN_FORWARD, nullptr); - if (outer_cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported outer-loop WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - JIT_WHILE_BEGIN(cursor_aggregate_outer_loop) - BasicBlock* endOuterLoopBlock = JIT_WHILE_POST_BLOCK(); - Instruction* res = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &outer_cursor, JIT_RANGE_SCAN_MAIN); - JIT_WHILE_EVAL_NOT(res) - Instruction* outer_row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), access_mode, JIT_INDEX_SCAN_FORWARD, &outer_cursor, JIT_RANGE_SCAN_MAIN); - - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, outer_row, &plan->_outer_scan._filters, &maxArg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported outer-loop filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // before we move on to inner scan, we save the outer row in a safe copy (but for that purpose we need to save row - // in outer scan state) - AddSetStateRow(ctx, outer_row, JIT_RANGE_SCAN_MAIN); - AddCopyOuterStateRow(ctx); - - // now build the inner loop - MOT_LOG_DEBUG("Generating inner loop cursor for range JOIN query"); - JitTvmRuntimeCursor inner_cursor = - buildRangeCursor(ctx, &plan->_inner_scan, &maxArg, JIT_RANGE_SCAN_INNER, JIT_INDEX_SCAN_FORWARD, outer_row); - if (inner_cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range JOIN query: unsupported inner-loop WHERE clause type"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - JIT_WHILE_BEGIN(cursor_aggregate_inner_loop) - Instruction* res_inner = AddIsScanEnd(ctx, JIT_INDEX_SCAN_FORWARD, &inner_cursor, JIT_RANGE_SCAN_INNER); - JIT_WHILE_EVAL_NOT(res_inner) - Instruction* inner_row = buildGetRowFromIterator( - ctx, JIT_WHILE_POST_BLOCK(), access_mode, JIT_INDEX_SCAN_FORWARD, &inner_cursor, JIT_RANGE_SCAN_INNER); - - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, inner_row, &plan->_inner_scan._filters, &maxArg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported inner-loop filter"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // find out to which table the aggreate expression refers, and aggregate it - // if row disqualified due to DISTINCT operator then go back to inner loop test block - bool aggRes = false; - if (plan->_aggregate._table == ctx->m_innerTable_info.m_table) { - aggRes = buildAggregateRow(ctx, &plan->_aggregate, inner_row, JIT_WHILE_COND_BLOCK()); - } else { - // retrieve the safe copy of the outer row - Instruction* outer_row_copy = AddGetOuterStateRowCopy(ctx); - aggRes = buildAggregateRow(ctx, &plan->_aggregate, outer_row_copy, JIT_WHILE_COND_BLOCK()); - } - - if (!aggRes) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range JOIN query: unsupported aggregate"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // if a limit clause exists, then increment limit counter and check if reached limit - if (plan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - Instruction* currentLimitCount = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(currentLimitCount, JIT_CONST(plan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - AddDestroyCursor(ctx, &outer_cursor); - AddDestroyCursor(ctx, &inner_cursor); - ctx->_builder->CreateBr(endOuterLoopBlock); // break from inner outside of outer loop - JIT_IF_END() - } - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of inner loop"); - AddDestroyCursor(ctx, &inner_cursor); - - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of outer loop"); - AddDestroyCursor(ctx, &outer_cursor); - - // wrap up aggregation and write to result tuple - buildAggregateResult(ctx, &plan->_aggregate); - - // store the result tuple - AddExecStoreVirtualTuple(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended - AddSetScanEnded(ctx, 1); - - // return success from calling function - builder.CreateRet(builder.CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - JitContext* jit_context = FinalizeCodegen(ctx, maxArg, JIT_COMMAND_AGGREGATE_JOIN); - - // cleanup - DestroyCodeGenContext(ctx); - - return jit_context; -} - -static JitContext* JitJoinCodegen(Query* query, const char* query_string, JitJoinPlan* plan) -{ - JitContext* jit_context = nullptr; - - if (plan->_aggregate._aggreaget_op == JIT_AGGREGATE_NONE) { - switch (plan->_scan_type) { - case JIT_JOIN_SCAN_POINT: - // special case: this is really a point query - jit_context = JitPointJoinCodegen(query, query_string, plan); - break; - - case JIT_JOIN_SCAN_OUTER_POINT: - // special case: outer scan is really a point query - jit_context = JitPointOuterJoinCodegen(query, query_string, plan); - break; - - case JIT_JOIN_SCAN_INNER_POINT: - // special case: inner scan is really a point query - jit_context = JitPointInnerJoinCodegen(query, query_string, plan); - break; - - case JIT_JOIN_SCAN_RANGE: - jit_context = JitRangeJoinCodegen(query, query_string, plan); - break; - - default: - MOT_LOG_TRACE( - "Cannot generate jitteed code for JOIN plan: Invalid JOIN scan type %d", (int)plan->_scan_type); - break; - } - } else { - jit_context = JitAggregateRangeJoinCodegen(query, query_string, plan); - } - - return jit_context; -} - -static bool JitSubSelectCodegen(JitTvmCodeGenContext* ctx, JitCompoundPlan* plan, int subQueryIndex) -{ - MOT_LOG_DEBUG("Generating code for MOT sub-select at thread %p", (intptr_t)pthread_self()); - IssueDebugLog("Executing simple SELECT sub-query"); - - // get the sub-query plan - JitSelectPlan* subPlan = (JitSelectPlan*)plan->_sub_query_plans[subQueryIndex]; - - // begin the WHERE clause - int maxArg = 0; - if (!buildPointScan( - ctx, &subPlan->_query._search_exprs, &maxArg, JIT_RANGE_SCAN_SUB_QUERY, nullptr, -1, subQueryIndex)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: unsupported WHERE clause type"); - return false; - } - - // fetch row for read - Instruction* row = buildSearchRow(ctx, MOT::AccessType::RD, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex); - - // check for additional filters - if (!buildFilterRow(ctx, row, &subPlan->_query._filters, &maxArg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: unsupported filter"); - return false; - } - - // now begin selecting columns into result - IssueDebugLog("Selecting column into result"); - if (!selectRowColumns(ctx, row, &subPlan->_select_exprs, &maxArg, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex)) { - MOT_LOG_TRACE("Failed to generate jitted code for SELECT sub-query: failed to process target entry"); - return false; - } - - return true; -} - -/** @brief Generates code for range SELECT sub-query with aggregator. */ -static bool JitSubAggregateRangeSelectCodegen(JitTvmCodeGenContext* ctx, JitCompoundPlan* plan, int subQueryIndex) -{ - MOT_LOG_DEBUG("Generating code for MOT aggregate range select sub-query at thread %p", (intptr_t)pthread_self()); - IssueDebugLog("Executing aggregated range select sub-query"); - - // get the sub-query plan - JitRangeSelectPlan* subPlan = (JitRangeSelectPlan*)plan->_sub_query_plans[subQueryIndex]; - - // prepare for aggregation - if (!prepareAggregate(ctx, &subPlan->_aggregate)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range SELECT sub-query: failed to prepare aggregate"); - return false; - } - - AddResetStateLimitCounter(ctx); - - // pay attention: aggregated range scan is not stateful, since we scan all tuples in one call - MOT::AccessType accessMode = MOT::AccessType::RD; - - // begin the WHERE clause - int maxArg = 0; - JitIndexScanDirection index_scan_direction = JIT_INDEX_SCAN_FORWARD; - - // build range iterators - MOT_LOG_DEBUG("Generating range cursor for range SELECT sub-query"); - JitTvmRuntimeCursor cursor = buildRangeCursor( - ctx, &subPlan->_index_scan, &maxArg, JIT_RANGE_SCAN_SUB_QUERY, index_scan_direction, nullptr, subQueryIndex); - if (cursor.begin_itr == nullptr) { - MOT_LOG_TRACE( - "Failed to generate jitted code for aggregate range SELECT sub-query: unsupported WHERE clause type"); - return false; - } - - JIT_WHILE_BEGIN(cursor_aggregate_loop) - Instruction* res = AddIsScanEnd(ctx, index_scan_direction, &cursor, JIT_RANGE_SCAN_SUB_QUERY, subQueryIndex); - JIT_WHILE_EVAL_NOT(res) - Instruction* row = buildGetRowFromIterator(ctx, - JIT_WHILE_POST_BLOCK(), - accessMode, - JIT_INDEX_SCAN_FORWARD, - &cursor, - JIT_RANGE_SCAN_SUB_QUERY, - subQueryIndex); - - // check for additional filters, if not try to fetch next row - if (!buildFilterRow(ctx, row, &subPlan->_index_scan._filters, &maxArg, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT sub-query: unsupported filter"); - return false; - } - - // aggregate into tuple (we use tuple's resno column as aggregated sum instead of defining local variable) - // if row disqualified due to DISTINCT operator then go back to loop test block - if (!buildAggregateRow(ctx, &subPlan->_aggregate, row, JIT_WHILE_COND_BLOCK())) { - MOT_LOG_TRACE("Failed to generate jitted code for aggregate range SELECT sub-query: unsupported aggregate"); - return false; - } - - // if a limit clause exists, then increment limit counter and check if reached limit - if (subPlan->_limit_count > 0) { - AddIncrementStateLimitCounter(ctx); - JIT_IF_BEGIN(limit_count_reached) - Instruction* current_limit_count = AddGetStateLimitCounter(ctx); - JIT_IF_EVAL_CMP(current_limit_count, JIT_CONST(subPlan->_limit_count), JIT_ICMP_EQ); - IssueDebugLog("Reached limit specified in limit clause, raising internal state scan end flag"); - JIT_WHILE_BREAK() // break from loop - JIT_IF_END() - } - JIT_WHILE_END() - - // cleanup - IssueDebugLog("Reached end of aggregate range select sub-query loop"); - AddDestroyCursor(ctx, &cursor); - - // wrap up aggregation and write to result tuple (even though this is unfitting to outer query tuple...) - buildAggregateResult(ctx, &subPlan->_aggregate); - - // coy aggregate result from outer query result tuple into sub-query result tuple - AddCopyAggregateToSubQueryResult(ctx, subQueryIndex); - - return true; -} - -static bool JitSubQueryCodeGen(JitTvmCodeGenContext* ctx, JitCompoundPlan* plan, int subQueryIndex) -{ - bool result = false; - JitPlan* subPlan = plan->_sub_query_plans[subQueryIndex]; - if (subPlan->_plan_type == JIT_PLAN_POINT_QUERY) { - result = JitSubSelectCodegen(ctx, plan, subQueryIndex); - } else if (subPlan->_plan_type == JIT_PLAN_RANGE_SCAN) { - result = JitSubAggregateRangeSelectCodegen(ctx, plan, subQueryIndex); - } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Generate JIT Code", - "Cannot generate JIT code for sub-query plan: Invalid plan type %d", - (int)subPlan->_plan_type); - } - return result; -} - -static JitContext* JitCompoundOuterSelectCodegen( - JitTvmCodeGenContext* ctx, Query* query, const char* query_string, JitSelectPlan* plan) -{ - // begin the WHERE clause - int max_arg = 0; - if (!buildPointScan(ctx, &plan->_query._search_exprs, &max_arg, JIT_RANGE_SCAN_MAIN, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: unsupported WHERE clause type"); - return nullptr; - } - - // fetch row for read - MOT::AccessType access_mode = query->hasForUpdate ? MOT::AccessType::RD_FOR_UPDATE : MOT::AccessType::RD; - Instruction* row = buildSearchRow(ctx, access_mode, JIT_RANGE_SCAN_MAIN); - - // check for additional filters - if (!buildFilterRow(ctx, row, &plan->_query._filters, &max_arg, nullptr)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: unsupported filter"); - return nullptr; - } - - // now begin selecting columns into result - IssueDebugLog("Selecting columns into result"); - if (!selectRowColumns(ctx, row, &plan->_select_exprs, &max_arg, JIT_RANGE_SCAN_MAIN)) { - MOT_LOG_TRACE("Failed to generate jitted code for COMPOUND SELECT query: failed to process target entry"); - return nullptr; - } - - AddExecStoreVirtualTuple(ctx); - - // update number of rows processed - buildIncrementRowsProcessed(ctx); - - // execute *tp_processed = rows_processed - AddSetTpProcessed(ctx); - - // signal to envelope executor scan ended (this is a point query) - AddSetScanEnded(ctx, 1); - - // return success from calling function - ctx->_builder->CreateRet(ctx->_builder->CreateConst((uint64_t)MOT::RC_OK)); - - // wrap up - return FinalizeCodegen(ctx, max_arg, JIT_COMMAND_COMPOUND_SELECT); -} - -static JitContext* JitCompoundOuterCodegen( - JitTvmCodeGenContext* ctx, Query* query, const char* queryString, JitCompoundPlan* plan) -{ - JitContext* jitContext = nullptr; - if (plan->_command_type == JIT_COMMAND_SELECT) { - jitContext = JitCompoundOuterSelectCodegen(ctx, query, queryString, (JitSelectPlan*)plan->_outer_query_plan); - } - // currently other outer query types are not supported - return jitContext; -} - -static JitContext* JitCompoundCodegen(Query* query, const char* query_string, JitCompoundPlan* plan) -{ - // a compound query plan contains one or more sub-queries that evaluate to a datum that next needs to be fed as a - // parameter to the outer query. We are currently imposing the following limitations: - // 1. one sub-query that can only be a MAX aggregate - // 2. outer query must be a simple point select query. - // - // our main strategy is as follows (based on the fact that each sub-query evaluates into a single value) - // 1. for each sub-query: - // 1.1 execute sub-query and put datum result in sub-query result slot, according to sub-query index - // 2. execute the outer query as a simple query - // 3. whenever we encounter a sub-link expression, it is evaluated as an expression that reads the pre-computed - // sub-query result in step 1.1, according to sub-query index - MOT_LOG_DEBUG("Generating code for MOT compound select at thread %p", (intptr_t)pthread_self()); - - // prepare code generation context - Builder builder; - JitTvmCodeGenContext cg_ctx = {0}; - MOT::Table* table = plan->_outer_query_plan->_query._table; - if (!InitCompoundCodeGenContext(&cg_ctx, &builder, table, table->GetPrimaryIndex(), plan)) { - return nullptr; - } - JitTvmCodeGenContext* ctx = &cg_ctx; - - // prepare the jitted function (declare, get arguments into context and define locals) - CreateJittedFunction(ctx, "MotJittedCompoundSelect", query_string); - IssueDebugLog("Starting execution of jitted COMPOUND SELECT"); - - // initialize rows_processed local variable - buildResetRowsProcessed(ctx); - - // generate code for sub-query execution - uint32_t subQueryCount = 0; - for (int i = 0; i < plan->_outer_query_plan->_query._search_exprs._count; ++i) { - if (plan->_outer_query_plan->_query._search_exprs._exprs[i]._expr->_expr_type == JIT_EXPR_TYPE_SUBLINK) { - JitSubLinkExpr* subLinkExpr = - (JitSubLinkExpr*)plan->_outer_query_plan->_query._search_exprs._exprs[i]._expr; - if (!JitSubQueryCodeGen(ctx, plan, subLinkExpr->_sub_query_index)) { - MOT_LOG_TRACE( - "Failed to generate jitted code for COMPOUND SELECT query: Failed to generate code for sub-query"); - DestroyCodeGenContext(ctx); - return nullptr; - } - ++subQueryCount; - } - } - - // clear tuple early, so that we will have a null datum in case outer query finds nothing - AddExecClearTuple(ctx); - - // generate code for the outer query - JitContext* jitContext = JitCompoundOuterCodegen(ctx, query, query_string, plan); - if (jitContext == nullptr) { - MOT_LOG_TRACE("Failed to generate code for outer query in compound select"); - DestroyCodeGenContext(ctx); - return nullptr; - } - - // prepare sub-query data in resulting JIT context (for later execution) - MOT_ASSERT(subQueryCount > 0); - MOT_ASSERT(subQueryCount == plan->_sub_query_count); - if ((subQueryCount > 0) && !PrepareSubQueryData(jitContext, plan)) { - MOT_LOG_TRACE("Failed to prepare tuple table slot array for sub-queries in JIT context object"); - DestroyJitContext(jitContext); - jitContext = nullptr; - } - - // cleanup - DestroyCodeGenContext(ctx); - - return jitContext; -} - -static JitContext* JitRangeScanCodegen(const Query* query, const char* query_string, JitRangeScanPlan* plan) -{ - JitContext* jit_context = nullptr; - - switch (plan->_command_type) { - case JIT_COMMAND_UPDATE: - jit_context = JitRangeUpdateCodegen(query, query_string, (JitRangeUpdatePlan*)plan); - break; - - case JIT_COMMAND_SELECT: { - JitRangeSelectPlan* range_select_plan = (JitRangeSelectPlan*)plan; - if (range_select_plan->_aggregate._aggreaget_op == JIT_AGGREGATE_NONE) { - jit_context = JitRangeSelectCodegen(query, query_string, range_select_plan); - } else { - jit_context = JitAggregateRangeSelectCodegen(query, query_string, range_select_plan); - } - } break; - - default: - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate JIT Code", - "Invalid point query JIT plan command type %d", - (int)plan->_command_type); - break; - } - - return jit_context; -} - -static JitContext* JitPointQueryCodegen(const Query* query, const char* query_string, JitPointQueryPlan* plan) -{ - JitContext* jit_context = nullptr; - - switch (plan->_command_type) { - case JIT_COMMAND_UPDATE: - jit_context = JitUpdateCodegen(query, query_string, (JitUpdatePlan*)plan); - break; - - case JIT_COMMAND_DELETE: - jit_context = JitDeleteCodegen(query, query_string, (JitDeletePlan*)plan); - break; - - case JIT_COMMAND_SELECT: - jit_context = JitSelectCodegen(query, query_string, (JitSelectPlan*)plan); - break; - - default: - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Generate JIT Code", - "Invalid point query JIT plan command type %d", - (int)plan->_command_type); - break; - } - - return jit_context; -} - -extern JitContext* JitCodegenTvmQuery(Query* query, const char* query_string, JitPlan* plan) -{ - JitContext* jit_context = nullptr; - - MOT_LOG_DEBUG("*** Attempting to generate planned TVM-jitted code for query: %s", query_string); - - switch (plan->_plan_type) { - case JIT_PLAN_INSERT_QUERY: - jit_context = JitInsertCodegen(query, query_string, (JitInsertPlan*)plan); - break; - - case JIT_PLAN_POINT_QUERY: - jit_context = JitPointQueryCodegen(query, query_string, (JitPointQueryPlan*)plan); - break; - - case JIT_PLAN_RANGE_SCAN: - jit_context = JitRangeScanCodegen(query, query_string, (JitRangeScanPlan*)plan); - break; - - case JIT_PLAN_JOIN: - jit_context = JitJoinCodegen(query, query_string, (JitJoinPlan*)plan); - break; - - case JIT_PLAN_COMPOUND: - jit_context = JitCompoundCodegen(query, query_string, (JitCompoundPlan*)plan); - break; - - default: - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Generate JIT Code", "Invalid JIT plan type %d", (int)plan->_plan_type); - break; - } - - if (jit_context == nullptr) { - MOT_LOG_TRACE("Failed to generate TVM-jitted code for query: %s", query_string); - } else { - MOT_LOG_DEBUG( - "Got TVM-jitted function %p after compile, for query: %s", jit_context->m_tvmFunction, query_string); - } - - return jit_context; -} - -extern int JitExecTvmQuery(JitContext* jit_context, ParamListInfo params, TupleTableSlot* slot, uint64_t* tp_processed, - int* scan_ended, int newScan) -{ - int result = 0; - ExecContext* exec_context = jit_context->m_execContext; - - // allocate execution context on-demand - if (exec_context == nullptr) { - exec_context = allocExecContext(jit_context->m_tvmFunction->getRegisterCount()); - if (exec_context == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Execute JIT", "Failed to allocate execution context for TVM-jit function"); - result = MOT::RC_MEMORY_ALLOCATION_ERROR; - } else { - // save for later execution - jit_context->m_execContext = exec_context; - } - } - - if (exec_context != nullptr) { - exec_context->_jit_context = jit_context; - exec_context->_params = params; - exec_context->_slot = slot; - exec_context->_tp_processed = tp_processed; - exec_context->_scan_ended = scan_ended; - exec_context->m_newScan = newScan; - - result = (int)jit_context->m_tvmFunction->exec(exec_context); - } - - return result; -} -} // namespace JitExec diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.h deleted file mode 100644 index 68b91689b..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_query_codegen.h - * TVM-jitted code generation. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_query_codegen.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_QUERY_CODEGEN_H -#define JIT_TVM_QUERY_CODEGEN_H - -#include "nodes/parsenodes.h" -#include "nodes/params.h" -#include "executor/tuptable.h" -#include "storage/mot/jit_def.h" - -namespace tvm { -struct ExecContext; -class Function; -} // namespace tvm - -namespace JitExec { -// forward declarations -struct JitContext; -struct JitPlan; - -/** - * @brief Generates TVM-jitted code for a query or loads a cached entry instead. - * @param query The parsed query. - * @param queryString The query string. - * @param analysis_result The information gathered during query analysis during a previous call to - * @ref IsJittable(). - * @param plan The plan resulted from the analysis phase. - * @return The resulting JIT context, or NULL if failed. - */ -extern JitContext* JitCodegenTvmQuery(Query* query, const char* queryString, JitPlan* plan); - -/** - * @brief Executed a previously TVM-jitted query. - * @param jitContext The context produced by a previous call to @ref QueryCodegen(). - * @param params The list of bound parameters passed to the query. - * @param[out] slot The slot used for reporting select result. - * @param[out] tuplesProcessed The variable used to report the number of processed tuples. - * @param[out] scanEnded The variable used to report if a range scan ended. - * @param newScan Specifies whether this is a new scan or a continued previous scan. - * @return Zero if succeeded, otherwise an error code. - * @note This function may cause transaction abort. - */ -extern int JitExecTvmQuery(JitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, - uint64_t* tuplesProcessed, int* scanEnded, int newScan); -} // namespace JitExec - -#endif /* JIT_TVM_QUERY_CODEGEN_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.cpp b/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.cpp deleted file mode 100644 index 5bc4838b7..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.cpp +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_util.cpp - * TVM-jitted query execution utilities. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_util.cpp - * - * ------------------------------------------------------------------------- - */ - -#include "global.h" -#include "jit_tvm_util.h" -#include "jit_tvm.h" -#include "knl/knl_session.h" -#include - -namespace tvm { -// Globals -#define IF_STACK u_sess->mot_cxt.jit_tvm_if_stack -#define WHILE_STACK u_sess->mot_cxt.jit_tvm_while_stack -#define DO_WHILE_STACK u_sess->mot_cxt.jit_tvm_do_while_stack - -char* JitUtils::FormatBlockName(char* buf, size_t size, const char* baseName, const char* suffix) -{ - errno_t erc = snprintf_s(buf, size, size - 1, "%s%s", baseName, suffix); - securec_check_ss(erc, "\0", "\0"); - return buf; -} - -JitStatement::JitStatement(Builder* builder) : m_builder(builder) -{} - -JitStatement::~JitStatement() -{ - m_builder = nullptr; -} - -void JitStatement::EvalCmp( - Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp, BasicBlock* successBlock, BasicBlock* failBlock) -{ - // evaluate condition and start if-true block - Instruction* res = m_builder->CreateICmp(lhs, rhs, cmpOp); - m_builder->CreateCondBr(res, successBlock, failBlock); - m_builder->SetInsertPoint(successBlock); -} - -void JitStatement::Eval(Instruction* value, BasicBlock* successBlock, BasicBlock* failBlock) -{ - // if value is not null (i.e. not equal to zero) we branch to success - EvalCmp(value, m_builder->CreateConst(0), JitExec::JIT_ICMP_NE, successBlock, failBlock); -} - -void JitStatement::EvalNot(Instruction* value, BasicBlock* successBlock, BasicBlock* failBlock) -{ - // if value is null (i.e. equal to zero) we branch to success - EvalCmp(value, m_builder->CreateConst(0), JitExec::JIT_ICMP_EQ, successBlock, failBlock); -} - -JitIf::JitIf(Builder* builder, const char* blockName) : JitStatement(builder), m_elseBlockUsed(false) -{ - constexpr size_t bufSize = 64; - char buf[bufSize]; - m_condBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_if_cond_bb")); - m_ifBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_if_exec_bb")); - m_elseBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_else_exec_bb")); - m_postIfBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_if_bb")); - - // we end current block and start the condition evaluation block - m_builder->CreateBr(m_condBlock); - m_builder->SetInsertPoint(m_condBlock); - - // push this object on top of the if-stack - m_next = IF_STACK; - IF_STACK = this; -} - -JitIf::~JitIf() -{ - // pop this object from the if-stack - IF_STACK = IF_STACK->m_next; - m_condBlock = nullptr; - m_ifBlock = nullptr; - m_elseBlock = nullptr; - m_postIfBlock = nullptr; - m_next = nullptr; -} - -JitIf* JitIf::GetCurrentStatement() -{ - return IF_STACK; -} - -void JitIf::JitIfCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp) -{ - EvalCmp(lhs, rhs, cmpOp, m_ifBlock, m_elseBlock); -} - -void JitIf::JitIfEval(Instruction* value) -{ - Eval(value, m_ifBlock, m_elseBlock); -} - -void JitIf::JitIfNot(Instruction* value) -{ - EvalNot(value, m_ifBlock, m_elseBlock); -} - -void JitIf::JitElse() -{ - // end if-true block first (jump to post-if point), and then start else block - if (!m_builder->currentBlockEndsInBranch()) { - m_builder->CreateBr(m_postIfBlock); - } - m_builder->SetInsertPoint(m_elseBlock); - m_elseBlockUsed = true; -} - -void JitIf::JitEnd() -{ - // end previous block (if not already ended) - if (!m_builder->currentBlockEndsInBranch()) { - m_builder->CreateBr(m_postIfBlock); - } - - if (!m_elseBlockUsed) { - // user did not use else block, so we must generate an empty one, otherwise it will be removed - // during function finalization (since it is empty), and if the condition evaluation fails, then - // during runtime we will jump to a block that was deleted (core dump) - m_builder->SetInsertPoint(m_elseBlock); - m_builder->CreateBr(m_postIfBlock); - } - - // now start the post-if block - m_builder->SetInsertPoint(m_postIfBlock); -} - -JitWhile::JitWhile(Builder* builder, const char* blockName) : JitStatement(builder) -{ - constexpr int bufSize = 64; - char buf[bufSize]; - m_condWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_cond_while_bb")); - m_execWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_exec_while_bb")); - m_postWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_while_bb")); - - // we end current block and start the condition evaluation block - m_builder->CreateBr(m_condWhileBlock); - m_builder->SetInsertPoint(m_condWhileBlock); - - // push this object on top of the while-stack - m_next = WHILE_STACK; - WHILE_STACK = this; -} - -JitWhile::~JitWhile() -{ - // pop this object from the while-stack - WHILE_STACK = WHILE_STACK->m_next; - m_condWhileBlock = nullptr; - m_execWhileBlock = nullptr; - m_postWhileBlock = nullptr; - m_next = nullptr; -} - -JitWhile* JitWhile::GetCurrentStatement() -{ - return WHILE_STACK; -} - -void JitWhile::JitWhileCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp) -{ - EvalCmp(lhs, rhs, cmpOp, m_execWhileBlock, m_postWhileBlock); -} - -void JitWhile::JitWhileEval(Instruction* value) -{ - Eval(value, m_execWhileBlock, m_postWhileBlock); -} - -void JitWhile::JitWhileNot(Instruction* value) -{ - EvalNot(value, m_execWhileBlock, m_postWhileBlock); -} - -void JitWhile::JitContinue() -{ - // jump to test evaluation block - m_builder->CreateBr(m_condWhileBlock); -} - -void JitWhile::JitBreak() -{ - // jump to post while block - m_builder->CreateBr(m_postWhileBlock); -} - -void JitWhile::JitEnd() -{ - // insert instruction to jump back to test loop and begin code after loop - m_builder->CreateBr(m_condWhileBlock); - m_builder->SetInsertPoint(m_postWhileBlock); -} - -JitDoWhile::JitDoWhile(Builder* builder, const char* blockName) : JitStatement(builder) -{ - const unsigned int bufSize = 64; - char buf[bufSize]; - m_execWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_exec_while_bb")); - m_condWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_cond_while_bb")); - m_postWhileBlock = m_builder->CreateBlock(JitUtils::FormatBlockName(buf, bufSize, blockName, "_post_while_bb")); - - // we end current block and start the do-while execution block - m_builder->CreateBr(m_execWhileBlock); - m_builder->SetInsertPoint(m_execWhileBlock); - - // push this object on top of the do-while-stack - m_next = DO_WHILE_STACK; - DO_WHILE_STACK = this; -} - -JitDoWhile::~JitDoWhile() -{ - // pop this object from the while-stack - DO_WHILE_STACK = DO_WHILE_STACK->m_next; - m_execWhileBlock = nullptr; - m_condWhileBlock = nullptr; - m_postWhileBlock = nullptr; - m_next = nullptr; -} - -JitDoWhile* JitDoWhile::GetCurrentStatement() -{ - return DO_WHILE_STACK; -} - -void JitDoWhile::JitContinue() -{ - // jump to test condition block - m_builder->CreateBr(m_condWhileBlock); -} - -void JitDoWhile::JitBreak() -{ - // jump to post while block - m_builder->CreateBr(m_postWhileBlock); -} - -void JitDoWhile::JitCond() -{ - // end previous block and start test block - m_builder->CreateBr(m_condWhileBlock); - m_builder->SetInsertPoint(m_condWhileBlock); -} - -void JitDoWhile::JitWhileCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp) -{ - EvalCmp(lhs, rhs, cmpOp, m_execWhileBlock, m_postWhileBlock); -} - -void JitDoWhile::JitWhileEval(Instruction* value) -{ - Eval(value, m_execWhileBlock, m_postWhileBlock); -} - -void JitDoWhile::JitWhileNot(Instruction* value) -{ - EvalNot(value, m_execWhileBlock, m_postWhileBlock); -} - -void JitDoWhile::JitEnd() -{ - // no need to jump to cond block - m_builder->SetInsertPoint(m_postWhileBlock); -} -} // namespace tvm diff --git a/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.h b/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.h deleted file mode 100644 index d27da9431..000000000 --- a/src/gausskernel/storage/mot/jit_exec/jit_tvm_util.h +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * ------------------------------------------------------------------------- - * - * jit_tvm_util.h - * TVM-jitted query execution utilities. - * - * IDENTIFICATION - * src/gausskernel/storage/mot/jit_exec/jit_tvm_util.h - * - * ------------------------------------------------------------------------- - */ - -#ifndef JIT_TVM_UTIL_H -#define JIT_TVM_UTIL_H - -#include -#include "jit_common.h" // for JitICmpOp, should be refactored? - -namespace tvm { -// forward declarations -class Builder; -class BasicBlock; -class Instruction; - -/** @class Static helpers. */ -class JitUtils { -public: - /** - * @brief Formats a block name. - * @param buf The format buffer. - * @param size The format buffer size. - * @param baseName The base name of the block. - * @param suffix The suffix to append. - * @return The resulting block name. - */ - static char* FormatBlockName(char* buf, size_t size, const char* baseName, const char* suffix); - -private: - JitUtils() - {} - - virtual ~JitUtils() - {} -}; - -/** @class Implements a generic statement. */ -class JitStatement { -protected: - /** @var The TVM builder. */ - Builder* m_builder; - - /** - * @brief Constructor. - * @param builder The TVM builder. - */ - explicit JitStatement(Builder* builder); - - /** @brief Destructor. */ - virtual ~JitStatement(); - - /** - * @brief Compares two values. If the values pass the condition then the success-block begins, - * otherwise the fail block begins. - * @param lhs The left-hand-side value to compare. - * @param rhs The right-hand-side value to compare. - * @param cmpOp The compare operation to perform. - * @param successBlock The success block. - * @param failBlock The fail block. - */ - void EvalCmp( - Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp, BasicBlock* successBlock, BasicBlock* failBlock); - - /** - * @brief Tests whether a value is null or zero. If the value passes the condition then the - * success-block begins, otherwise the fail block begins. - * @param value The value to evaluate. - * @param successBlock The success block. - * @param failBlock The fail block. - */ - void Eval(Instruction* value, BasicBlock* successBlock, BasicBlock* failBlock); - - /** - * @brief Tests whether a value is NOT null or zero. If the value passes the condition then the - * success-block begins, otherwise the fail block begins. - * @param value The value to evaluate. - * @param successBlock The success block. - * @param failBlock The fail block. - */ - void EvalNot(Instruction* value, BasicBlock* successBlock, BasicBlock* failBlock); -}; - -/** @class Implements an if construct. */ -class JitIf : public JitStatement { -public: - /** - * @brief Constructor. - * @param builder The TVM builder. - * @param blockName The if-block name. - */ - JitIf(Builder* builder, const char* blockName); - - /** @brief Destructor. */ - ~JitIf() override; - - /** @brief Retrieves the current (innermost if nested) if-block. */ - static JitIf* GetCurrentStatement(); - - /** - * @brief Begins an if-block by comparing two values. If the values pass the condition then the - * if-block begins. - * @param lhs The left-hand-side value to compare. - * @param rhs The right-hand-side value to compare. - * @param cmpOp The compare operation to perform. - */ - void JitIfCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp); - - /** - * @brief Begins an if-block by checking if a value is null or zero. If the value passes the - * condition then the if-block begins. - * @param value The value to evaluate. - */ - void JitIfEval(Instruction* value); - - /** - * @brief Begins an if-block by checking if a value is not null or zero. If the value passes the - * condition then the if-block begins. - * @param value The value to evaluate. - */ - void JitIfNot(Instruction* value); - - /** @brief Begins the else-block, in case the two values are not equal. Calling this function is optional. */ - void JitElse(); - - /** @brief Terminates the if block. Calling this function is mandatory. */ - void JitEnd(); - - /** @brief Retrieves the condition evaluation block. */ - inline BasicBlock* GetCondBlock() - { - return m_condBlock; - }; - - /** @brief Retrieves the condition success execution block. */ - inline BasicBlock* GetIfBlock() - { - return m_ifBlock; - }; - - /** @brief Retrieves the condition failure execution block. */ - inline BasicBlock* GetElseBlock() - { - return m_elseBlock; - }; - - /** @brief Retrieves the post-if execution block. */ - inline BasicBlock* GetPostBlock() - { - return m_postIfBlock; - }; - -private: - BasicBlock* m_condBlock; - BasicBlock* m_ifBlock; - BasicBlock* m_elseBlock; - BasicBlock* m_postIfBlock; - bool m_elseBlockUsed; - - /** @brief The next if-block (outer with respect to this block). */ - JitIf* m_next; -}; - -/** @class Implements a while construct. */ -class JitWhile : public JitStatement { -public: - /** - * @brief Constructor. - * @param builder The TVM builder. - * @param blockName The while-block name. - */ - JitWhile(Builder* builder, const char* blockName); - - /** @brief Destructor. */ - ~JitWhile() override; - - /** @brief Retrieves the current (innermost if nested) while-block. */ - static JitWhile* GetCurrentStatement(); - - /** - * @brief Performs the while-block test by comparing two values. If the values pass the condition - * then the while exec-block begins. Calling this function is mandatory. - * @param lhs The left-hand-side value to compare. - * @param rhs The right-hand-side value to compare. - * @param cmpOp The compare operation to perform. - */ - void JitWhileCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp); - - /** - * @brief Performs the while-block test by checking if a value is null or zero. If the value - * passes the condition then the while exec-block begins. - * @param value The value to evaluate. - */ - void JitWhileEval(Instruction* value); - - /** - * @brief Performs the while-block test by checking if a value is not null or zero. If the value - * passes the condition then the while exec-block begins. - * @param value The value to evaluate. - */ - void JitWhileNot(Instruction* value); - - /** @brief Stops the current iteration and begins a new one. */ - void JitContinue(); - - /** @brief Breaks out of the while loop. */ - void JitBreak(); - - /** @brief Terminates the while block. Calling this function is mandatory. */ - void JitEnd(); - - /** @brief Retrieves the condition evaluation block. */ - inline BasicBlock* GetCondBlock() - { - return m_condWhileBlock; - }; - - /** @brief Retrieves the condition success execution block. */ - inline BasicBlock* GetExecBlock() - { - return m_execWhileBlock; - }; - - /** @brief Retrieves the post-if execution block. */ - inline BasicBlock* GetPostBlock() - { - return m_postWhileBlock; - }; - -private: - BasicBlock* m_condWhileBlock; - BasicBlock* m_execWhileBlock; - BasicBlock* m_postWhileBlock; - - /** @brief The next while-block (outer with respect to this block). */ - JitWhile* m_next; -}; - -/** @class Implements a do-while construct. */ -class JitDoWhile : public JitStatement { -public: - /** - * @brief Constructor. - * @param builder The TVM builder. - * @param blockName The do-while-block name. - */ - JitDoWhile(Builder* builder, const char* blockName); - - /** @brief Destructor. */ - ~JitDoWhile() override; - - /** @brief Retrieves the current (innermost if nested) do-while-block. */ - static JitDoWhile* GetCurrentStatement(); - - /** @brief Stops the current iteration and begins a new one by first evaluating the condition. */ - void JitContinue(); - - /** @brief Breaks out of the do-while loop. */ - void JitBreak(); - - /** - * @brief Marks the beginning of the test condition block. This is followed by code to compute the - * two values used in the test. - */ - void JitCond(); - - /** - * @brief Performs the while-block test by comparing two values. If the values pass the condition - * then the while exec-block begins. Calling this function is mandatory. - * @param lhs The left-hand-side value to compare. - * @param rhs The right-hand-side value to compare. - * @param cmpOp The compare operation to perform. - */ - void JitWhileCmp(Instruction* lhs, Instruction* rhs, JitExec::JitICmpOp cmpOp); - - /** - * @brief Performs the while-block test by checking if a value is null or zero. If the value - * passes the condition then the while exec-block begins. - * @param value The value to evaluate. - */ - void JitWhileEval(Instruction* value); - - /** - * @brief Performs the while-block test by checking if a value is not null or zero. If the value - * passes the condition then the while exec-block begins. - * @param value The value to evaluate. - */ - void JitWhileNot(Instruction* value); - - /** @brief Terminates the do-while block. Calling this function is mandatory. */ - void JitEnd(); - - /** @brief Retrieves the condition evaluation block. */ - inline BasicBlock* GetCondBlock() - { - return m_condWhileBlock; - }; - - /** @brief Retrieves the condition success execution block. */ - inline BasicBlock* GetExecBlock() - { - return m_execWhileBlock; - }; - - /** @brief Retrieves the post-if execution block. */ - inline BasicBlock* GetPostBlock() - { - return m_postWhileBlock; - }; - -private: - BasicBlock* m_execWhileBlock; - BasicBlock* m_condWhileBlock; - BasicBlock* m_postWhileBlock; - - /** @brief The next do-while-block (outer with respect to this block). */ - JitDoWhile* m_next; -}; -} // namespace tvm - -/******************************************** - * TVM helper macros - ********************************************/ -/** @define Helper macro for a constant instruction. */ -#define JIT_CONST(value) ctx->_builder->CreateConst((uint64_t)(value)) - -/** @define Helper macro for a return statement. */ -#define JIT_RETURN(value) ctx->_builder->CreateRet(value) - -/** @define Helper macro for a return constant statement. */ -#define JIT_RETURN_CONST(value) ctx->_builder->CreateRet(JIT_CONST(value)) - -/** @define Helper macro for a branch/goto statement. */ -#define JIT_GOTO(block) ctx->_builder->CreateBr(block) - -/** @define Macro for getting the current block. */ -#define JIT_CURRENT_BLOCK() ctx->_builder->GetInsertBlock() - -/******************************************** - * TVM If Statement - ********************************************/ -/** - * @define Macro for starting a pseudo-LLVM if statement. Make sure to put condition evaluation code - * after this statement if you wish to jump back to the condition evaluation block. - */ -#define JIT_IF_BEGIN(name) \ - { \ - tvm::JitIf jitIf(ctx->_builder, #name); - -/** @define Macro for referring the current if-block. */ -#define JIT_IF_CURRENT() tvm::JitIf::GetCurrentStatement() - -/******************************************** - * TVM While Statement - ********************************************/ -/** @define Macro for starting a native while statement. */ -#define JIT_WHILE_BEGIN(name) \ - { \ - tvm::JitWhile jitWhile(ctx->_builder, #name); - -/** @define Macro for referring the current while-block. */ -#define JIT_WHILE_CURRENT() tvm::JitWhile::GetCurrentStatement() - -/******************************************** - * TVM Do-While Statement - ********************************************/ -/** @define Macro for starting a native do-while statement. */ -#define JIT_DO_WHILE_BEGIN(name) \ - { \ - tvm::JitDoWhile jitDoWhile(ctx->_builder, #name); - -/** @define Macro for referring the current do-while-block. */ -#define JIT_DO_WHILE_CURRENT() tvm::JitDoWhile::GetCurrentStatement() - -// the other macros for emitting JIT code are found in jit_util.h -// make sure to include that file after including this file -#endif /* JIT_TVM_UTIL_H */ diff --git a/src/gausskernel/storage/mot/jit_exec/jit_util.h b/src/gausskernel/storage/mot/jit_exec/jit_util.h index ffb2622ee..582ea110a 100644 --- a/src/gausskernel/storage/mot/jit_exec/jit_util.h +++ b/src/gausskernel/storage/mot/jit_exec/jit_util.h @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * jit_util.h - * Definitions for emitting LLVM or TVM code. + * Definitions for emitting LLVM code. * * IDENTIFICATION * src/gausskernel/storage/mot/jit_exec/jit_util.h @@ -25,21 +25,23 @@ #ifndef JIT_UTIL_H #define JIT_UTIL_H -// this file contains definitions for emitting LLVM or TVM code -// the macros are identical for both flavors. -// in order to use these macros properly, include either jit_llvm_util.h or -// jit_tvm_util.h before including this file. +// This file contains definitions for emitting LLVM code. +// The macros are identical for both flavors. +// In order to use these macros properly, include either jit_llvm_util.h before including this file. // They contain definitions for the following essential macros: // // - JIT_IF_BEGIN/CURRENT // - JIT_WHILE_BEGIN/CURRENT // - JIT_DO_WHILE_BEGIN/CURRENT // - JIT_FOR_BEGIN/CURRENT +// - JIT_SWITCH_BEGIN/CURRENT +// - JIT_TRY_BEGIN/CURRENT // // If statements should be used as follows: // // JIT_IF_BEGIN(label_name) -// JIT_IF_EVAL(some_computed_value) // or JIT_IF_EVAL_NOT(), or JIT_IF_EVAL_CMP() +// ... emit code for evaluating while condition value +// JIT_IF_EVAL(cond) // or JIT_IF_EVAL_NOT(), or JIT_IF_EVAL_CMP() // ... // emit code for success // JIT_ELSE() // begin else block - optional // ... // emit code for failure @@ -48,7 +50,8 @@ // While statements should be used as follows: // // JIT_WHILE_BEGIN(label_name) -// JIT_WHILE_EVAL(some_computed_value) // or JIT_WHILE_EVAL_NOT(), or JIT_WHILE_EVAL_CMP() +// ... emit code for evaluating while condition value +// JIT_WHILE_EVAL(cond) // or JIT_WHILE_EVAL_NOT(), or JIT_WHILE_EVAL_CMP() // ... // emit code for while body // JIT_WHILE_BREAK() // emit code to break from while loop // JIT_WHILE_CONTINUE() // emit code to stop current iteration and re-evaluate while condition @@ -60,7 +63,8 @@ // ... // emit code for do-while body // JIT_DO_WHILE_BREAK() // emit code to break from do-while loop // JIT_DO_WHILE_CONTINUE() // emit code to stop current iteration and re-evaluate do-while condition -// JIT_DO_WHILE_EVAL(some_computed_value) // or JIT_DO_WHILE_EVAL_NOT(), or JIT_DO_WHILE_EVAL_CMP() +// ... emit code for evaluating while condition value +// JIT_DO_WHILE_EVAL(cond) // or JIT_DO_WHILE_EVAL_NOT(), or JIT_DO_WHILE_EVAL_CMP() // JIT_DO_WHILE_END() // // For loop statements should be used as follows: @@ -73,44 +77,63 @@ // Switch-case statements should be used as follows: // // JIT_SWITCH_BEGIN(label_name, value) -// JIT_CASE_BEGIN(value1) +// JIT_CASE(value1) // ... // emit code for this case -// JIT_CASE_BREAK() // break from case (optional, might fall-through) -// JIT_CASE_END() // required? if not then change name of JIT_CASE_BEGIN() to JIT_CASE() +// JIT_CASE_BREAK() // break from case (mandatory, no fall-through is available, use many-case instead) // ... -// JIT_CASE_BEGIN(value1) -// ... // emit code for this case -// JIT_CASE_BREAK() // break from case (optional, might fall-through) -// JIT_CASE_END() // required? +// JIT_CASE_MANY(value2) +// JIT_CASE_MANY(value3) +// ... +// JIT_CASE_MANY(valueN) +// JIT_END_CASE_MANY() +// ... // emit code for cases value2 through valueN +// JIT_CASE_BREAK() // break from case (mandatory) // ... // JIT_CASE_DEFAULT() // ... // emit code for default case -// JIT_CASE_BREAK() // mandatory? -// JIT_CASE_END() // required? +// JIT_CASE_BREAK() // break from case (mandatory) // JIT_SWITCH_END() +// +// Try-catch statements should be used as follows: +// +// JIT_TRY_BEGIN(label_name) +// ... // emit code for try-block, can call JIT_THROW(value) to emit "throw" instruction +// JIT_CATCH(value1) +// ... // emit code for catch-block, can call JIT_RETHROW() to emit "rethrow" instruction +// JIT_END_CATCH() // end catch-block (mandatory) +// ... +// JIT_BEGIN_CATCH_MANY(value2) +// JIT_BEGIN_CATCH_MANY(value3) +// ... +// JIT_BEGIN_CATCH_MANY(valueN) +// JIT_END_CATCH_MANY() +// ... // emit code for multiple value catch-block, can call JIT_RETHROW() to emit "rethrow" instruction +// JIT_END_CATCH() // end catch-block (mandatory) +// ... +// JIT_CATCH_ALL() // optional, catches all values +// ... // emit code for default catch-block, can call JIT_RETHROW() to emit "rethrow" instruction +// JIT_END_CATCH() // end catch-block (mandatory) +// JIT_TRY_END() //////////////////////////////////////////////////////////////////////////////// -// If Statement Macros (both LLVM and TVM) +// If Statement Macros //////////////////////////////////////////////////////////////////////////////// /** @define Macro for evaluating the condition of a JIT if statement. */ -#define JIT_IF_EVAL_CMP(lhs, rhs, cmpOp) JIT_IF_CURRENT()->JitIfCmp(lhs, rhs, cmpOp); +#define JIT_IF_EVAL_CMP(lhs, rhs, cmpOp) JIT_IF_CURRENT()->JitIfCmp(lhs, rhs, cmpOp) /** @define Macro for evaluating the condition of a JIT if statement. */ -#define JIT_IF_EVAL(value) JIT_IF_CURRENT()->JitIfEval(value); +#define JIT_IF_EVAL(value) JIT_IF_CURRENT()->JitIfEval(value) /** @define Macro for evaluating the condition of a JIT if statement. */ -#define JIT_IF_EVAL_NOT(value) JIT_IF_CURRENT()->JitIfNot(value); +#define JIT_IF_EVAL_NOT(value) JIT_IF_CURRENT()->JitIfNot(value) /** @define Macro for starting the else block in a JIT if statement. */ -#define JIT_ELSE() JIT_IF_CURRENT()->JitElse(); +#define JIT_ELSE() JIT_IF_CURRENT()->JitElse() /** @define Macro for ending a JIT if statement. */ #define JIT_IF_END() \ JIT_IF_CURRENT()->JitEnd(); \ } -/** @define Macro for getting the condition evaluation block of the current if statement. */ -#define JIT_IF_COND_BLOCK() JIT_IF_CURRENT()->GetCondBlock() - /** @define Macro for getting the condition success execution block of the current if statement. */ #define JIT_IF_EXEC_BLOCK() JIT_IF_CURRENT()->GetIfBlock() @@ -121,22 +144,22 @@ #define JIT_IF_POST_BLOCK() JIT_IF_CURRENT()->GetPostBlock() //////////////////////////////////////////////////////////////////////////////// -// While Statement Macros (both LLVM and TVM) +// While Statement Macros //////////////////////////////////////////////////////////////////////////////// /** @define Macro for evaluating the condition of a JIT while statement. */ -#define JIT_WHILE_EVAL_CMP(lhs, rhs, cmpOp) JIT_WHILE_CURRENT()->JitWhileCmp(lhs, rhs, cmpOp); +#define JIT_WHILE_EVAL_CMP(lhs, rhs, cmpOp) JIT_WHILE_CURRENT()->JitWhileCmp(lhs, rhs, cmpOp) /** @define Macro for evaluating the condition of a JIT while statement. */ -#define JIT_WHILE_EVAL(value) JIT_WHILE_CURRENT()->JitWhileEval(value); +#define JIT_WHILE_EVAL(value) JIT_WHILE_CURRENT()->JitWhileEval(value) /** @define Macro for evaluating the condition of a JIT while statement. */ -#define JIT_WHILE_EVAL_NOT(value) JIT_WHILE_CURRENT()->JitWhileNot(value); +#define JIT_WHILE_EVAL_NOT(value) JIT_WHILE_CURRENT()->JitWhileNot(value) /** @define Macro for emitting a "continue" instruction within a JIT while statement. */ -#define JIT_WHILE_CONTINUE() JIT_WHILE_CURRENT()->JitContinue(); +#define JIT_WHILE_CONTINUE() JIT_WHILE_CURRENT()->JitContinue() /** @define Macro for emitting a "break" instruction within a JIT while statement. */ -#define JIT_WHILE_BREAK() JIT_WHILE_CURRENT()->JitBreak(); +#define JIT_WHILE_BREAK() JIT_WHILE_CURRENT()->JitBreak() /** @define Macro for ending a JIT while statement. */ #define JIT_WHILE_END() \ @@ -153,25 +176,25 @@ #define JIT_WHILE_POST_BLOCK() JIT_WHILE_CURRENT()->GetPostBlock() //////////////////////////////////////////////////////////////////////////////// -// Do-While Statement Macros (both LLVM and TVM) +// Do-While Statement Macros //////////////////////////////////////////////////////////////////////////////// /** @define Macro for emitting a "continue" instruction within a JIT do-while statement. */ -#define JIT_DO_WHILE_CONTINUE() JIT_DO_WHILE_CURRENT()->JitContinue(); +#define JIT_DO_WHILE_CONTINUE() JIT_DO_WHILE_CURRENT()->JitContinue() /** @define Macro for emitting a "break" instruction within a JIT do-while statement. */ -#define JIT_DO_WHILE_BREAK() JIT_DO_WHILE_CURRENT()->JitBreak(); +#define JIT_DO_WHILE_BREAK() JIT_DO_WHILE_CURRENT()->JitBreak() /** @define Macro for marking start of condition evaluation in a JIT do-while statement. */ -#define JIT_DO_WHILE_COND() JIT_DO_WHILE_CURRENT()->JitCond(); +#define JIT_DO_WHILE_COND() JIT_DO_WHILE_CURRENT()->JitCond() /** @define Macro for evaluating the condition of a JIT do-while statement. */ -#define JIT_DO_WHILE_EVAL_CMP(lhs, rhs, cmpOp) JIT_DO_WHILE_CURRENT()->JitWhileCmp(lhs, rhs, cmpOp); +#define JIT_DO_WHILE_EVAL_CMP(lhs, rhs, cmpOp) JIT_DO_WHILE_CURRENT()->JitWhileCmp(lhs, rhs, cmpOp) /** @define Macro for evaluating the condition of a JIT do-while statement. */ -#define JIT_DO_WHILE_EVAL(value) JIT_DO_WHILE_CURRENT()->JitWhileEval(value); +#define JIT_DO_WHILE_EVAL(value) JIT_DO_WHILE_CURRENT()->JitWhileEval(value) /** @define Macro for evaluating the condition of a JIT do-while statement. */ -#define JIT_DO_WHILE_EVAL_NOT(value) JIT_DO_WHILE_CURRENT()->JitWhileNot(value); +#define JIT_DO_WHILE_EVAL_NOT(value) JIT_DO_WHILE_CURRENT()->JitWhileNot(value) /** @define Macro for ending a JIT while statement. */ #define JIT_DO_WHILE_END() \ @@ -187,4 +210,104 @@ /** @define Macro for getting the block after the current while statement. */ #define JIT_DO_WHILE_POST_BLOCK() JIT_DO_WHILE_CURRENT()->GetPostBlock() +//////////////////////////////////////////////////////////////////////////////// +// For Statement Macros +//////////////////////////////////////////////////////////////////////////////// + +/** @define Macro for emitting a "continue" instruction within a JIT for statement. */ +#define JIT_FOR_CONTINUE() JIT_FOR_CURRENT()->JitContinue() + +/** @define Macro for emitting a "break" instruction within a JIT for statement. */ +#define JIT_FOR_BREAK() JIT_FOR_CURRENT()->JitBreak() + +/** @define Macro for emitting a "break" instruction within a JIT for statement. */ +#define JIT_FOR_COUNTER() JIT_FOR_CURRENT()->GetCounter() + +/** @define Macro for ending a JIT for statement. */ +#define JIT_FOR_END() \ + JIT_FOR_CURRENT()->JitEnd(); \ + } + +/** @define Macro for getting the condition evaluation block of the current for statement. */ +#define JIT_FOR_INCREMENT_BLOCK() JIT_FOR_CURRENT()->GetIncrementBlock() + +/** @define Macro for getting the block after the current for statement. */ +#define JIT_FOR_POST_BLOCK() JIT_FOR_CURRENT()->GetPostBlock() + +//////////////////////////////////////////////////////////////////////////////// +// Common Loop Statement Macros +//////////////////////////////////////////////////////////////////////////////// + +/** @define Macro for breaking from current loop. */ +#define JIT_LOOP_BREAK() JIT_LOOP_CURRENT()->JitBreak() + +/** @define Macro for continuing to next iteration in current loop. */ +#define JIT_LOOP_CONTINUE() JIT_LOOP_CURRENT()->JitContinue() + +/** @define Macro for getting the condition checking block of the current loop. */ +#define JIT_LOOP_COND_BLOCK() JIT_LOOP_CURRENT()->GetCondBlock() + +/** @define Macro for getting the post-block of the current loop (code after the loop). */ +#define JIT_LOOP_POST_BLOCK() JIT_LOOP_CURRENT()->GetPostBlock() + +//////////////////////////////////////////////////////////////////////////////// +// Switch-Case Statement Macros +//////////////////////////////////////////////////////////////////////////////// + +/** @define Macro for emitting a "case value:" block within a JIT switch-case statement. */ +#define JIT_CASE(value) JIT_SWITCH_CURRENT()->JitCase(value) + +/** @define Macro for emitting a "case value:" with multiple values. */ +#define JIT_CASE_MANY(value) JIT_SWITCH_CURRENT()->JitCaseMany(value) + +/** @define Macro for terminating multiple "case value:" labels. */ +#define JIT_CASE_MANY_TERM() JIT_SWITCH_CURRENT()->JitCaseManyTerm() + +/** @define Macro for emitting a "break" instruction within a JIT switch-case statement. */ +#define JIT_CASE_BREAK() JIT_SWITCH_CURRENT()->JitBreak() + +/** @define Macro for ending a case block within a JIT switch-case statement. */ +#define JIT_CASE_END() JIT_SWITCH_CURRENT()->JitCaseEnd() + +/** @define Macro for emitting a "default:" case block within a JIT switch-case statement. */ +#define JIT_CASE_DEFAULT() JIT_SWITCH_CURRENT()->JitDefault() + +/** @define Macro for ending a JIT for statement. */ +#define JIT_SWITCH_END() \ + JIT_SWITCH_CURRENT()->JitEnd(); \ + } + +//////////////////////////////////////////////////////////////////////////////// +// Try-Catch Statement Macros +//////////////////////////////////////////////////////////////////////////////// + +/** @define Macro for emitting a "throw value" instruction within a JIT try-catch statement. */ +#define JIT_THROW(value) JIT_TRY_CURRENT()->JitThrow(value) + +/** @define Macro for emitting a "catch (value)" block within a JIT try-catch statement. */ +#define JIT_BEGIN_CATCH(value) JIT_TRY_CURRENT()->JitBeginCatch(value) + +/** @define Macro for emitting multiple "catch (value)" block within a JIT try-catch statement. */ +#define JIT_BEGIN_CATCH_MANY(value) JIT_TRY_CURRENT()->JitBeginCatchMany(value) + +/** @define Macro for ending a "catch multiple values" statement and starting the catch body block. */ +#define JIT_CATCH_MANY_TERM() JIT_TRY_CURRENT()->JitCatchManyTerm() + +/** @define Macro for ending a "catch [multiple] (value)" block within a JIT try-catch statement. */ +#define JIT_END_CATCH() JIT_TRY_CURRENT()->JitEndCatch() + +/** @define Macro for emitting a "catch(...)" block within a JIT try-catch statement. */ +#define JIT_CATCH_ALL() JIT_TRY_CURRENT()->JitCatchAll() + +/** @define Macro for emitting a "throw" instruction within a catch-block in a JIT try-catch statement. */ +#define JIT_RETHROW() JIT_TRY_CURRENT()->JitRethrow() + +/** @define Macro for ending a JIT try-catch statement. */ +#define JIT_TRY_END() \ + JIT_TRY_CURRENT()->JitEnd(); \ + } + +/** @define Macro for getting the block after the current for statement. */ +#define JIT_TRY_POST_BLOCK() JIT_TRY_CURRENT()->GetPostBlock() + #endif /* JIT_UTIL_H */ diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 0ff8a562d..637eedac3 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -54,7 +54,9 @@ typedef enum { XACT_EVENT_PREPARE, XACT_EVENT_COMMIT_PREPARED, XACT_EVENT_ROLLBACK_PREPARED, - XACT_EVENT_PREROLLBACK_CLEANUP // For MOT, to cleanup some internal resources. + XACT_EVENT_PREROLLBACK_CLEANUP, // For MOT, to cleanup some internal resources. + XACT_EVENT_POST_COMMIT_CLEANUP, // For MOT, to cleanup some dropped function JIT sources. + XACT_EVENT_STMT_FINISH // For MOT, to notify end of statement. } XactEvent; typedef void (*XactCallback)(XactEvent event, void* arg); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 7898a4b06..862174100 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -69,4 +69,8 @@ extern void PlanTreeWalker( Plan* plan, void (*walker)(Plan*, void*, const char*), void* context, const char* queryString); extern DatanodeStatement* light_set_datanode_queries(const char* stmt_name); + +#ifdef ENABLE_MOT +extern void TryMotJitCodegenQuery(const char* queryString, CachedPlanSource* psrc, Query* query); +#endif #endif /* PREPARE_H */ diff --git a/src/include/executor/exec/execdesc.h b/src/include/executor/exec/execdesc.h index b1333aa0f..9bc73b8ed 100644 --- a/src/include/executor/exec/execdesc.h +++ b/src/include/executor/exec/execdesc.h @@ -22,7 +22,7 @@ // forward declaration namespace JitExec { - struct JitContext; + struct MotJitContext; } #endif @@ -58,7 +58,7 @@ typedef struct QueryDesc { struct Instrumentation* totaltime; /* total time spent in ExecutorRun */ bool executed; /* if the query already executed */ #ifdef ENABLE_MOT - JitExec::JitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ + JitExec::MotJitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ #endif } QueryDesc; @@ -66,7 +66,7 @@ typedef struct QueryDesc { #ifdef ENABLE_MOT extern QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, DestReceiver* dest, ParamListInfo params, int instrument_options, - JitExec::JitContext* mot_jit_context = nullptr); + JitExec::MotJitContext* motJitContext = nullptr); #else extern QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, DestReceiver* dest, ParamListInfo params, int instrument_options); diff --git a/src/include/knl/knl_instance.h b/src/include/knl/knl_instance.h index 293c2e235..79be0d071 100755 --- a/src/include/knl/knl_instance.h +++ b/src/include/knl/knl_instance.h @@ -1024,7 +1024,7 @@ typedef struct knl_g_archive_context { #ifdef ENABLE_MOT typedef struct knl_g_mot_context { - JitExec::JitExecMode jitExecMode; + struct VariableCacheData* shmemVariableCache; } knl_g_mot_context; #endif diff --git a/src/include/knl/knl_session.h b/src/include/knl/knl_session.h index cd27aefb5..d04f83a40 100644 --- a/src/include/knl/knl_session.h +++ b/src/include/knl/knl_session.h @@ -2568,20 +2568,18 @@ namespace MOT { } namespace JitExec { - struct JitContext; + struct MotJitContext; struct JitContextPool; } -namespace tvm { - class JitIf; - class JitWhile; - class JitDoWhile; -} - -namespace llvm { +namespace llvm_util { class JitIf; + class JitLoop; class JitWhile; class JitDoWhile; + class JitFor; + class JitSwitchCase; + class JitTryCatch; } typedef struct knl_u_mot_context { @@ -2596,14 +2594,23 @@ typedef struct knl_u_mot_context { // JIT JitExec::JitContextPool* jit_session_context_pool; uint32_t jit_context_count; - llvm::JitIf* jit_llvm_if_stack; - llvm::JitWhile* jit_llvm_while_stack; - llvm::JitDoWhile* jit_llvm_do_while_stack; - tvm::JitIf* jit_tvm_if_stack; - tvm::JitWhile* jit_tvm_while_stack; - tvm::JitDoWhile* jit_tvm_do_while_stack; - JitExec::JitContext* jit_context; + llvm_util::JitIf* jit_llvm_if_stack; + llvm_util::JitLoop* jit_llvm_loop_stack; + llvm_util::JitWhile* jit_llvm_while_stack; + llvm_util::JitDoWhile* jit_llvm_do_while_stack; + llvm_util::JitFor* jit_llvm_for_stack; + llvm_util::JitSwitchCase* jit_llvm_switch_case_stack; + llvm_util::JitTryCatch* jit_llvm_try_catch_stack; + + JitExec::MotJitContext* jit_context; MOT::TxnManager* jit_txn; + int jit_compile_depth; + int jit_parse_error; + void* jit_pg_query; + void* jit_ns_stack; + void* jit_session_source_map; + bool jit_xact_callback_registered; + int jit_codegen_error; } knl_u_mot_context; #endif @@ -2878,6 +2885,10 @@ extern bool enable_out_param_override(); extern THR_LOCAL knl_session_context* u_sess; +#ifdef ENABLE_MOT +extern void knl_u_mot_init(knl_u_mot_context* mot_cxt); +#endif + inline bool stp_disable_xact_and_set_err_msg(bool *save_commit_rollback_state, stp_xact_err_type type) { *save_commit_rollback_state = u_sess->SPI_cxt.is_allow_commit_rollback; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 5e56865e6..e1203d62c 100755 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -36,7 +36,7 @@ // forward declaration for MOT JitContext namespace JitExec { - struct JitContext; + struct MotJitContext; } #endif @@ -649,7 +649,7 @@ typedef struct EState { bool isRowTriggerShippable; /* true if all row triggers are shippable. */ #ifdef ENABLE_MOT - JitExec::JitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ + JitExec::MotJitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ #endif PruningResult* pruningResult; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fb5f078cd..9f132bf0b 100755 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -397,6 +397,10 @@ typedef enum NodeTag { T_SelectIntoVarList, T_AlterTableStmt, T_AlterTableCmd, +#ifdef ENABLE_MOT + T_AlterForeingTableCmd, + T_RenameForeingTableCmd, +#endif T_AlterDomainStmt, T_SetOperationStmt, T_GrantStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2b7f972be..c7cbce2eb 100755 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -949,6 +949,26 @@ typedef struct CreateForeignTableStmt { ForeignPartState* part_state; } CreateForeignTableStmt; +#ifdef ENABLE_MOT +typedef struct AlterForeingTableCmd { + NodeTag type; + AlterTableType subtype; + Relation rel; + const char* name; + Node* def; + Oid colTypeOid; + Expr* defValue; +} AlterForeingTableCmd; + +typedef struct RenameForeingTableCmd { + NodeTag type; + Oid relid; + ObjectType renameType; + char* oldname; + char* newname; +} RenameForeingTableCmd; +#endif + /* ---------------------- * Create/Drop USER MAPPING Statements * ---------------------- diff --git a/src/include/opfusion/opfusion_mot.h b/src/include/opfusion/opfusion_mot.h index c839f5c27..eb1c849f9 100644 --- a/src/include/opfusion/opfusion_mot.h +++ b/src/include/opfusion/opfusion_mot.h @@ -30,28 +30,38 @@ #ifdef ENABLE_MOT class MotJitSelectFusion : public OpFusion { public: - MotJitSelectFusion(MemoryContext context, CachedPlanSource *psrc, List *plantree_list, ParamListInfo params); + MotJitSelectFusion(MemoryContext context, CachedPlanSource* psrc, List* plantree_list, ParamListInfo params); - ~MotJitSelectFusion() {}; + ~MotJitSelectFusion() + {} - bool execute(long max_rows, char *completionTag); + bool execute(long max_rows, char* completionTag); void InitLocals(ParamListInfo params); void InitGlobals(); + +private: + struct MotJitSelectFusionGlobalVariable { + int64 m_limitCount; + int64 m_limitOffset; + }; + MotJitSelectFusionGlobalVariable* m_c_global; }; class MotJitModifyFusion : public OpFusion { public: - MotJitModifyFusion(MemoryContext context, CachedPlanSource *psrc, List *plantree_list, ParamListInfo params); + MotJitModifyFusion(MemoryContext context, CachedPlanSource* psrc, List* plantree_list, ParamListInfo params); - ~MotJitModifyFusion() {}; + ~MotJitModifyFusion() + {} - bool execute(long max_rows, char *completionTag); + bool execute(long max_rows, char* completionTag); void InitLocals(ParamListInfo params); void InitGlobals(); + private: struct MotJitModifyFusionLocaleVariable { CmdType m_cmdType; diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 7a280b27c..d543943b8 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -46,7 +46,6 @@ extern void applyLockingClause(Query* qry, Index rtindex, LockClauseStrength str int waitSec); #ifdef ENABLE_MOT extern void CheckTablesStorageEngine(Query* qry, StorageEngineType* type); -extern bool CheckMotIndexedColumnUpdate(Query* qry); typedef struct RTEDetectorContext { bool isMotTable; @@ -54,12 +53,6 @@ typedef struct RTEDetectorContext { List* queryNodes; int sublevelsUp; } RTEDetectorContext; - -typedef struct UpdateDetectorContext { - bool isIndexedColumnUpdate; - List* queryNodes; - int sublevelsUp; -} UpdateDetectorContext; #endif /* Record the rel name and corresponding columan name info */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b816efa53..981036683 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -2358,6 +2358,8 @@ extern void getSessionID(char* sessid, pg_time_t startTime, ThreadId Threadid); extern void getThrdID(char* thrdid, pg_time_t startTime, ThreadId Threadid); #define NUM_MOT_SESSION_MEMORY_DETAIL_ELEM 4 +#define NUM_MOT_JIT_DETAIL_ELEM 11 +#define NUM_MOT_JIT_PROFILE_ELEM 12 typedef struct MotSessionMemoryDetail { ThreadId threadid; @@ -2383,8 +2385,39 @@ typedef struct MotMemoryDetailPad { MotMemoryDetail* memoryDetail; } MotMemoryDetailPad; +typedef struct MotJitDetail { + Oid procOid; + char* query; + char* nameSpace; + char* jittableStatus; + char* validStatus; + TimestampTz lastUpdatedTimestamp; + char* planType; + int64 codegenTime; + int64 verifyTime; + int64 finalizeTime; + int64 compileTime; +} MotJitDetail; + +typedef struct MotJitProfile { + Oid procOid; + int32 id; + int32 parentId; + char* query; + char* nameSpace; + float4 weight; + int64 totalTime; + int64 selfTime; + int64 childGrossTime; + int64 childNetTime; + int64 defVarsTime; + int64 initVarsTime; +} MotJitProfile; + extern MotSessionMemoryDetail* GetMotSessionMemoryDetail(uint32* num); extern MotMemoryDetail* GetMotMemoryDetail(uint32* num, bool isGlobal); +extern MotJitDetail* GetMotJitDetail(uint32* num); +extern MotJitProfile* GetMotJitProfile(uint32* num); #ifdef MEMORY_CONTEXT_CHECKING typedef enum { STANDARD_DUMP, SHARED_DUMP } DUMP_TYPE; diff --git a/src/include/storage/mot/jit_def.h b/src/include/storage/mot/jit_def.h index 603e27321..2e8d26da7 100644 --- a/src/include/storage/mot/jit_def.h +++ b/src/include/storage/mot/jit_def.h @@ -27,37 +27,93 @@ #include +/*---------------------- Generic Constants -------------------*/ /** @define Impose a hard coded limit on depth of parsed expression. */ #define MOT_JIT_MAX_EXPR_DEPTH 10 /** @define We support up to 8 arguments for NULL management in expressions. */ #define MOT_JIT_EXPR_ARG_COUNT 8 -/** @define The maximum number of registers used in a pseudo-function execution. */ -#define MOT_JIT_MAX_FUNC_REGISTERS 4096 - /** @define The maximum number of arguments in a Boolean expression. */ #define MOT_JIT_MAX_BOOL_EXPR_ARGS 2 /** @define The maximum number of arguments in a function call expression. */ #define MOT_JIT_MAX_FUNC_EXPR_ARGS 3 +/** @define The global query name-space name. */ +#define MOT_JIT_GLOBAL_QUERY_NS "GLOBAL" + +/** @define The maximum nesting level of compiled stored procedures. */ +#define MOT_JIT_MAX_COMPILE_DEPTH 10 + +/** @define The maximum nesting level of compiled stored procedure blocks. */ +#define MOT_JIT_MAX_BLOCK_DEPTH 10 + +/*---------------------- Optional Compile-Time Configuration -------------------*/ +/* + * To enable generating JIT code for full-scan queries, #define MOT_JIT_FULL_SCAN + */ + +/* + * To debug JIT execution, #define MOT_JIT_DEBUG + */ + +/* + * To enable features required for JIT testing, #define MOT_JIT_TEST + */ + +/** @define Enable advanced WHERE clause operators (comment to disable). */ +#define MOT_JIT_ADVANCED_WHERE_OP + +/*---------------------- JIT Parse Errors -------------------*/ +/** @define JIT encountered generic parsing error. */ +#define MOT_JIT_GENERIC_PARSE_ERROR 1 + +/** @define JIT encountered "table not found" error. */ +#define MOT_JIT_TABLE_NOT_FOUND 2 + +/*---------------------- JIT Profiler Constants -------------------*/ +/** @define Invalid profile function identifier. */ +#define MOT_JIT_PROFILE_INVALID_FUNCTION_ID ((uint32_t)-1) + +/** @define Invalid profile region identifier. */ +#define MOT_JIT_PROFILE_INVALID_REGION_ID ((uint32_t)-1) + +/** @define The predefined "total" profile region. */ +#define MOT_JIT_PROFILE_REGION_TOTAL "total" + +/** @define The predefined "def-vars" profile region. */ +#define MOT_JIT_PROFILE_REGION_DEF_VARS "def-vars" + +/** @define The predefined "init-vars" profile region. */ +#define MOT_JIT_PROFILE_REGION_INIT_VARS "init-vars" + +/** @define The predefined "child-call" profile region. */ +#define MOT_JIT_PROFILE_REGION_CHILD_CALL "child-call" + namespace JitExec { +/** @enum JIT context state constants. */ +enum JitContextState : uint8_t { + /** @var Denotes initial context state.*/ + JIT_CONTEXT_STATE_INIT, -// To debug JIT execution, #define MOT_JIT_DEBUG -// To use advanced WHERE clause operators, #define MOT_JIT_ADVANCED_WHERE_OP -// To enable features required for JIT testing, #define MOT_JIT_TEST + /** @var Denotes JIT context is ready for use. */ + JIT_CONTEXT_STATE_READY, -/** @enum JIT execution mode constants. */ -enum JitExecMode : uint32_t { - /** @var Invalid execution mode. */ - JIT_EXEC_MODE_INVALID, + /** @var Denotes JIT context is pending compilation to finish. */ + JIT_CONTEXT_STATE_PENDING, - /** @var LLVM execution mode. */ - JIT_EXEC_MODE_LLVM, + /** @var Denotes JIT context finished compilation successfully. */ + JIT_CONTEXT_STATE_DONE, - /** @var TVM execution mode. */ - JIT_EXEC_MODE_TVM + /** @var Denotes JIT context is invalid and needs revalidation. */ + JIT_CONTEXT_STATE_INVALID, + + /** @var Denotes JIT context revalidation or compilation failed. */ + JIT_CONTEXT_STATE_ERROR, + + /** @var Denotes final context state. */ + JIT_CONTEXT_STATE_FINAL }; /** @enum JitCommandType Command types supported by jitted queries. */ @@ -83,6 +139,9 @@ enum JitCommandType : uint8_t { /** @var Unordered range select command. */ JIT_COMMAND_RANGE_SELECT, + /** @var Range delete command. */ + JIT_COMMAND_RANGE_DELETE, + /** @var Unordered full-scan select command. */ JIT_COMMAND_FULL_SELECT, @@ -99,7 +158,13 @@ enum JitCommandType : uint8_t { JIT_COMMAND_AGGREGATE_JOIN, /** @var Compound select command (point-select with sub-queries). */ - JIT_COMMAND_COMPOUND_SELECT + JIT_COMMAND_COMPOUND_SELECT, + + /** @var Function (stored-procedure) execution command. */ + JIT_COMMAND_FUNCTION, + + /** @var Invoke stored-procedure command. */ + JIT_COMMAND_INVOKE }; /** @enum JIT context usage constants. */ @@ -107,10 +172,57 @@ enum JitContextUsage : uint8_t { /** @var JIT context is used in global context. */ JIT_CONTEXT_GLOBAL, + /** @var JIT context is used in global context (but not as the primary stencil). */ + JIT_CONTEXT_GLOBAL_SECONDARY, + /** @var JIT context is used in session-local context. */ JIT_CONTEXT_LOCAL }; +/** @enum JIT purge scope constants. */ +enum JitPurgeScope { + /** @var Denotes that JIT query source objects are directly affected by the purge action. */ + JIT_PURGE_SCOPE_QUERY, + + /** @var Denotes that JIT stored procedure source objects are directly affected by the purge action. */ + JIT_PURGE_SCOPE_SP +}; + +/** @enum JIT purge action constants. */ +enum JitPurgeAction { + /** @var Denotes purge-only action. */ + JIT_PURGE_ONLY, + + /** @var Denotes purge and set-as-expired action. */ + JIT_PURGE_EXPIRE, + + /** @var Denotes purge and set-as-replaced action (stored procedures only). */ + JIT_PURGE_REPLACE +}; + +/** @brief Helper function to determine whether JIT context usage is global. */ +inline bool IsJitContextUsageGlobal(JitContextUsage usage) +{ + return ((usage == JIT_CONTEXT_GLOBAL) || (usage == JIT_CONTEXT_GLOBAL_SECONDARY)); +} + +inline const char* JitContextUsageToString(JitContextUsage usage) +{ + return (usage == JIT_CONTEXT_GLOBAL) + ? "global" + : ((usage == JIT_CONTEXT_GLOBAL_SECONDARY) ? "secondary-global" : "session-local"); +} + +inline const char* JitPurgeScopeToString(JitPurgeScope purgeScope) +{ + return (purgeScope == JIT_PURGE_SCOPE_QUERY) ? "query" : "sp"; +} + +inline const char* JitPurgeActionToString(JitPurgeAction purgeAction) +{ + return (purgeAction == JIT_PURGE_ONLY) ? "purge" : ((purgeAction == JIT_PURGE_EXPIRE) ? "expire" : "replace"); +} + /** @enum Constants for classifying operators in WHERE clause. */ enum JitWhereOperatorClass : uint8_t { /** @var Invalid where operator class. */ diff --git a/src/include/storage/mot/jit_exec.h b/src/include/storage/mot/jit_exec.h index 2f354bcf2..58aa9d30d 100644 --- a/src/include/storage/mot/jit_exec.h +++ b/src/include/storage/mot/jit_exec.h @@ -29,13 +29,15 @@ #include "nodes/params.h" #include "executor/tuptable.h" #include "nodes/parsenodes.h" +#include "nodes/execnodes.h" +#include "pgstat.h" #include "jit_def.h" namespace JitExec { // forward declaration -struct JitContext; +struct MotJitContext; struct JitContextPool; struct JitPlan; @@ -48,8 +50,11 @@ extern void JitDestroy(); /** @brief Queries whether MOT JIT compilation and execution is enabled. */ extern bool IsMotCodegenEnabled(); -/** @brief Quereis whether pseudo-LLVM is forced on platforms where LLVM is natively supported. */ -extern bool IsMotPseudoCodegenForced(); +/** @brief Queries whether MOT JIT compilation and execution of prepared queries is enabled. */ +extern bool IsMotQueryCodegenEnabled(); + +/** @brief Queries whether MOT JIT compilation and execution of stored procedures is enabled. */ +extern bool IsMotSPCodegenEnabled(); /** @brief Queries whether informative printing is enabled for MOT JIT compilation. */ extern bool IsMotCodegenPrintEnabled(); @@ -61,21 +66,57 @@ extern uint32_t GetMotCodegenLimit(); * @brief Queries whether a SQL query to be executed by MM Engine is jittable. * @param query The parsed SQL query to examine. * @param queryString The query text. - * @return The JIT plan if the query is jittable, otherwise NULL. + * @param forcePlan[opt] Specifies whether to force plan generation, even if a JIT source exists. + * @return The JIT plan if the query is jittable, otherwise null. */ -extern JitPlan* IsJittable(Query* query, const char* queryString); +extern JitPlan* IsJittableQuery(Query* query, const char* queryString, bool forcePlan = false); + +/** + * @brief Queries whether a stored procedure to be executed by MM Engine is jittable. + * @param procTuple The stored procedure entry in the system catalog. + * @param functionOid The function identifier. + * @param forcePlan[opt] Specifies whether to force plan generation, even if a JIT source exists. + * @return The JIT plan if the stored procedure is jittable, otherwise null. + */ +extern JitPlan* IsJittableFunction( + PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, bool forcePlan = false); + +/** @brief Queries whether this is an invoke query that invokes an already jitted stored procedure. */ +extern bool IsInvokeReadyFunction(Query* query); /** * @brief Generate jitted code for a query. * @param query The parsed SQL query for which jitted code is to be generated. * @param queryString The query text. * @param jitPlan The JIT plan produced during the call to @ref IsJittable(). + * @param The required resulting context usage. * @return The context of the jitted code required for later execution. */ -extern JitContext* JitCodegenQuery(Query* query, const char* queryString, JitPlan* jitPlan); +extern MotJitContext* JitCodegenQuery(Query* query, const char* queryString, JitPlan* jitPlan, JitContextUsage usage); + +/** + * @brief Utility helper for generating jitted code for a query. It packs together the calls for @ref IsJittableQuery + * and @ref JitCodegenQuery. + * @param query The parsed SQL query for which jitted code is to be generated. + * @param queryString The query text. + * @return The context of the jitted code required for later execution. + */ +extern MotJitContext* TryJitCodegenQuery(Query* query, const char* queryString); + +/** + * @brief Generate jitted code for a stored procedure. + * @param function The parsed stored procedure. + * @param procTuple The stored procedure entry in the system catalog. + * @param functionOid The function identifier. + * @param returnSetInfo Return set information for the function (required during parsing). + * @param jitPlan The JIT plan produced during the call to @ref IsJittable(). + * @return The context of the jitted code required for later execution. + */ +extern MotJitContext* JitCodegenFunction(PLpgSQL_function* function, HeapTuple procTuple, Oid functionOid, + ReturnSetInfo* returnSetInfo, JitPlan* jitPlan, JitContextUsage usage); /** @brief Resets the scan iteration counter for the JIT context. */ -extern void JitResetScan(JitContext* jitContext); +extern void JitResetScan(MotJitContext* jitContext); /** * @brief Executed a previously jitted query. @@ -88,26 +129,108 @@ extern void JitResetScan(JitContext* jitContext); * @note This function may cause transaction abort. */ extern int JitExecQuery( - JitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded); + MotJitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded); /** - * @brief Purges the global cache of JIT source stencils from all entries that refer the given relation id. - * @param relationId The external identifier of the relation to be purged. - * @param purgeOnly Specifies whether to just purge all keys/indexes referring to the given relation, or should the JIT - * context also be set as expired (which triggers re-compilation of the JIT function). + * @brief Executed a previously jitted stored procedure. + * @param jitContext The context produced by a previous call to @ref JitCodegenFunction(). + * @param params The list of bound parameters passed to the query. + * @param[out] slot The slot used for reporting select result. + * @param[out] tuplesProcessed The variable used to report the number of processed tuples. + * @param[out] scanEnded The variable used to report if a range scan ended. + * @return Zero if succeeded, otherwise an error code. + * @note This function may cause transaction abort. */ -extern void PurgeJitSourceCache(uint64_t relationId, bool purgeOnly); +extern int JitExecFunction( + MotJitContext* jitContext, ParamListInfo params, TupleTableSlot* slot, uint64_t* tuplesProcessed, int* scanEnded); + +/** + * @brief Purges the global cache of JIT source stencils from all entries that refer the given relation or stored + * procedure id. + * @param objectId The external identifier of the relation or stored procedure that triggers the purge. + * @param purgeScope The directly affected JIT source objects. In case of JIT query source, then the object identifier + * parameter denotes a relation id, otherwise it denotes a stored procedure id. + * @param purgeAction Specifies whether to just purge all keys/indexes referring to the given relation, or should the + * JIT context also be set as expired (which triggers re-compilation of the JIT function on sub-sequent access). + * @param funcName The stored procedure name (applicable only if purgeScope is JIT_PURGE_SCOPE_SP). + */ +extern void PurgeJitSourceCache( + uint64_t objectId, JitPurgeScope purgeScope, JitPurgeAction purgeAction, const char* funcName); + +/** + * @brief Re-Generate JIT code for all invalidated sub-queries of a stored procedure. + * @param jitContext The context produced by a previous call to @ref JitCodegenFunction(). + * @return True if operations succeeded, otherwise false. + */ +extern bool JitReCodegenFunctionQueries(MotJitContext* jitContext); // externalize functions defined elsewhere -/** @brief Destroys a jit context produced by a previous call to JitCodegenQuery. */ -extern void DestroyJitContext(JitContext* jitContext); +/** @brief Forces JIT context full invalidation (purge and invalidate). */ +extern void ForceJitContextInvalidation(MotJitContext* jitContext); -/** @brief De-allocates a jit context pool for a specific session. */ +/** @brief Queries whether a JIT context is valid. */ +extern bool IsJitContextValid(MotJitContext* jitContext); + +/** @brief Queries whether a JIT context is a sub-context. */ +extern bool IsJitSubContext(MotJitContext* jitContext); + +/** @brief Trigger code-generation of any missing sub-query. */ +extern bool TryRevalidateJitContext(MotJitContext* jitContext, TransactionId functionTxnId = InvalidTransactionId); + +/** @brief Queries whether this context is still waiting for source compilation to finish. */ +extern bool IsJitContextPendingCompile(MotJitContext* jitContext); + +/** @brief Queries whether this context is done waiting for source compilation to finish. */ +extern bool IsJitContextDoneCompile(MotJitContext* jitContext); + +/** @brief Queries whether this compilation for this context finished with error. */ +extern bool IsJitContextErrorCompile(MotJitContext* jitContext); + +/** @brief Queries current JIT context compile state. Returns true if any of the 3 possible states is true.*/ +extern bool GetJitContextCompileState(MotJitContext* jitContext, bool* isPending, bool* isDone, bool* isError); + +/** @brief Updates and retrieves the current context state. */ +extern JitContextState GetJitContextState(MotJitContext* jitContext); + +/** + * @brief Returns a JIT context back to its source pool. + * @param jitContext The JIT context to free. + */ +extern void FreeJitContext(MotJitContext* jitContext); + +/** + * @brief Destroys a JIT context produced by a previous call to JitCodegenQuery. + * @detail All internal resources associated with the context object are released, and the context + * is returned to its source context pool. + * @param jitContext The JIT context to destroy. + * @param[optional] isDropCachedPlan Specifies whether this is from DropCachedPlan (deallocate prepared statement). + */ +extern void DestroyJitContext(MotJitContext* jitContext, bool isDropCachedPlan = false); + +/** @brief De-allocates a JIT context pool for a specific session. */ extern void FreeSessionJitContextPool(JitContextPool* jitContextPool); -/** @brief Releases all resources associated with a plan.*/ +/** + * @brief Releases all resources associated with a plan. + * @param plan The plan to destroy. + */ extern void JitDestroyPlan(JitPlan* plan); + +/** @brief Get JIT global status.*/ +extern MotJitDetail* MOTGetJitDetail(uint32_t* num); + +/** @brief Get JIT profile data. */ +extern MotJitProfile* MOTGetJitProfile(uint32_t* num); + +/** @brief Reports sub-query parse error. */ +extern void JitReportParseError(ErrorData* edata, const char* queryString); + +/** @brief Cleans up all resource associated with JIT transactional behavior. */ +extern void CleanupJitSourceTxnState(); + +/** @brief Queries whether this is an invoke query plan. */ +extern bool IsInvokeQueryPlan(CachedPlanSource* planSource, Oid* functionOid, TransactionId* functionTxnId); } // namespace JitExec #endif // JIT_EXEC_H diff --git a/src/include/storage/mot/mot_fdw.h b/src/include/storage/mot/mot_fdw.h index b928d84d7..446d864ae 100644 --- a/src/include/storage/mot/mot_fdw.h +++ b/src/include/storage/mot/mot_fdw.h @@ -26,6 +26,7 @@ #ifndef MOT_FDW_H #define MOT_FDW_H +#include #include /** @brief Initializes MOT engine. */ @@ -82,4 +83,14 @@ extern void MOTCheckpointFetchUnlock(); extern bool MOTCheckpointExists( char* ctrlFilePath, size_t ctrlLen, char* checkpointDir, size_t checkpointLen, size_t& basePathLen); +/** + * The following helpers APIs are used for validating MOT GUC parameters. + */ +extern bool MOTValidateLogLevel(const char* logLevelStr); +extern bool MOTValidateAffinityMode(const char* affinityModeStr); +extern bool MOTValidateMemReserveMode(const char* reserveModeStr); +extern bool MOTValidateMemStorePolicy(const char* storePolicyStr); +extern bool MOTValidateMemAllocPolicy(const char* allocPolicyStr); +extern void MOTCheckTransactionAborted(); + #endif // MOT_FDW_H diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index abed78d50..b54d6d88c 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1748,6 +1748,8 @@ extern Datum hypopg_reset_index(PG_FUNCTION_ARGS); extern Datum mot_global_memory_detail(PG_FUNCTION_ARGS); extern Datum mot_local_memory_detail(PG_FUNCTION_ARGS); extern Datum mot_session_memory_detail(PG_FUNCTION_ARGS); +extern Datum mot_jit_detail(PG_FUNCTION_ARGS); +extern Datum mot_jit_profile(PG_FUNCTION_ARGS); /* UBtree index */ Datum gs_index_verify(PG_FUNCTION_ARGS); diff --git a/src/include/utils/knl_globalsystupcache.h b/src/include/utils/knl_globalsystupcache.h index 51354d06e..a68720df6 100644 --- a/src/include/utils/knl_globalsystupcache.h +++ b/src/include/utils/knl_globalsystupcache.h @@ -378,7 +378,6 @@ private: void FreeDeadCts(); void HandleDeadGlobalCatCTup(GlobalCatCTup *ct); void RemoveTailTupleElements(Index hash_index); - void InvalidLSC(uint32 hash_value); void FreeDeadCls(); void HandleDeadGlobalCatCList(GlobalCatCList *cl); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 70b9d394b..9226d2f7d 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -28,7 +28,7 @@ // forward declaration for MOT JitContext namespace JitExec { - struct JitContext; + struct MotJitContext; } #endif @@ -376,7 +376,8 @@ typedef struct CachedPlanSource { bool is_oneshot; /* is it a "oneshot" plan? */ #ifdef ENABLE_MOT StorageEngineType storageEngineType; /* which storage engine is used*/ - JitExec::JitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ + bool checkedMotJitCodegen; + JitExec::MotJitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ #endif int generation; /* increments each time we create a plan */ /* If CachedPlanSource has been saved, it is a member of a global list */ @@ -432,7 +433,7 @@ typedef struct CachedPlan { #ifdef ENABLE_MOT StorageEngineType storageEngineType; /* which storage engine is used*/ - JitExec::JitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ + JitExec::MotJitContext* mot_jit_context; /* MOT JIT context required for executing LLVM jitted code */ #endif bool dependsOnRole; /* is plan specific to that role? */ diff --git a/src/include/utils/plpgsql.h b/src/include/utils/plpgsql.h index 07a51f63a..c296b4f19 100644 --- a/src/include/utils/plpgsql.h +++ b/src/include/utils/plpgsql.h @@ -1660,6 +1660,9 @@ extern void exec_eval_datum(PLpgSQL_execstate *estate, bool *isnull, bool isretry); extern void exec_eval_cleanup(PLpgSQL_execstate *estate); +extern void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi); +extern void plpgsql_destroy_econtext(PLpgSQL_execstate* estate); +extern PLpgSQL_datum* copy_plpgsql_datum(PLpgSQL_datum* datum); extern void free_expr(PLpgSQL_expr* expr); extern void free_assignlist(List* assignlist); extern HeapTuple make_tuple_from_row(PLpgSQL_execstate* estate, PLpgSQL_row* row, TupleDesc tupdesc); @@ -1668,6 +1671,9 @@ extern int getTableOfIndexByDatumValue(TableOfIndexKey key, HTAB* tableOfIndex, extern Datum fillNestedTableArray(ArrayType* arrayval, Oid parenttypoid, Oid elemtypoid, int value, int idx); extern int plpgsql_estate_adddatum(PLpgSQL_execstate* estate, PLpgSQL_datum* newm); extern void CheckCurrCompileDependOnPackage(Oid pkgOid); +extern bool needRecompilePlan(SPIPlanPtr plan); +extern void plpgsql_param_fetch(ParamListInfo params, int paramid); +extern Datum pl_coerce_type_typmod(Datum value, Oid targetTypeId, int32 targetTypMod); #ifndef ENABLE_MULTIPLE_NODES extern void estate_cursor_set(FormatCallStack* plcallstack); #endif diff --git a/src/test/ha/GNUmakefile b/src/test/ha/GNUmakefile index 64b5a34f3..ee03e462f 100644 --- a/src/test/ha/GNUmakefile +++ b/src/test/ha/GNUmakefile @@ -38,6 +38,7 @@ hacheck_multi_single: all hacheck_multi_single_mot: all export prefix=$(prefix) && sh $(CURDIR)/run_ha_multi_single_mot.sh 1 $(PART) + export prefix=$(prefix) && sh $(CURDIR)/run_ha_multi_cascade_mot.sh 1 $(PART) hacheck_multi_single_shared_storage: all export prefix=$(prefix) && sh $(CURDIR)/run_ha_multi_single_shared_storage.sh 2 $(PART) diff --git a/src/test/ha/deploy_multi_cascade_mot.sh b/src/test/ha/deploy_multi_cascade_mot.sh new file mode 100644 index 000000000..8cf69df89 --- /dev/null +++ b/src/test/ha/deploy_multi_cascade_mot.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# deploy primary-standby-cascade_standby +# 1 1 node_num - 1 + +source ./standby_env.sh + +node_num=2 +#python $scripts_dir/pgxc_multi.py +#stop the database +python $scripts_dir/pgxc_multi_cascade_mot.py -o + +sleep 2 +#init the database +python $scripts_dir/pgxc_multi_cascade_mot.py -c 1 -d $node_num + +#build the standby +#gs_ctl build -D $data_dir/datanode1_standby -Z single_node +#gs_ctl build -D $data_dir/datanode4_standby -Z single_node + + +#stop the database +python $scripts_dir/pgxc_multi_cascade_mot.py -o + +#set the primary postgresql.conf file +gs_guc set -Z datanode -D $primary_data_dir -c "most_available_sync = on" +gs_guc set -Z datanode -D $primary_data_dir -c "synchronous_commit = on" +gs_guc set -Z datanode -D $primary_data_dir -c "log_min_messages = DEBUG5" +gs_guc set -Z datanode -D $primary_data_dir -c "data_replicate_buffer_size=256MB" +gs_guc set -Z datanode -D $primary_data_dir -c "walsender_max_send_size=8MB" +gs_guc set -Z datanode -D $primary_data_dir -c "wal_receiver_buffer_size=64MB" +gs_guc set -Z datanode -D $primary_data_dir -c "shared_buffers=2GB" +gs_guc set -Z datanode -D $primary_data_dir -c "modify_initial_password = off" +gs_guc set -Z datanode -D $primary_data_dir -c "wal_sender_timeout = 120s" +gs_guc set -Z datanode -D $primary_data_dir -c "wal_receiver_timeout = 120s" +gs_guc set -Z datanode -D $primary_data_dir -c "max_replication_slots = 8" +gs_guc set -Z datanode -D $primary_data_dir -c "max_wal_senders = 8" +gs_guc set -Z datanode -D $primary_data_dir -c "replication_type = 1" +gs_guc set -Z datanode -D $primary_data_dir -c "enable_data_replicate = off" +gs_guc set -Z datanode -D $primary_data_dir -c "available_zone = 'az1'" +gs_guc set -Z datanode -D $primary_data_dir -c "enable_incremental_checkpoint = off" + +echo $node_num +for((i=1; i<=$node_num; i++)) +do + datanode_dir=$data_dir/datanode$i + datanode_dir=$datanode_dir"_standby" + echo $datanode_dir + gs_guc set -Z datanode -D $datanode_dir -c "most_available_sync = on" + gs_guc set -Z datanode -D $datanode_dir -c "synchronous_commit = on" + gs_guc set -Z datanode -D $datanode_dir -c "log_min_messages = DEBUG5" + gs_guc set -Z datanode -D $datanode_dir -c "data_replicate_buffer_size=256MB" + gs_guc set -Z datanode -D $datanode_dir -c "walsender_max_send_size=8MB" + gs_guc set -Z datanode -D $datanode_dir -c "wal_receiver_buffer_size=64MB" + gs_guc set -Z datanode -D $datanode_dir -c "shared_buffers=2GB" + gs_guc set -Z datanode -D $datanode_dir -c "modify_initial_password = off" + gs_guc set -Z datanode -D $datanode_dir -c "wal_sender_timeout = 120s" + gs_guc set -Z datanode -D $datanode_dir -c "wal_receiver_timeout = 120s" + gs_guc set -Z datanode -D $datanode_dir -c "max_replication_slots = 8" + gs_guc set -Z datanode -D $datanode_dir -c "max_wal_senders = 8" + gs_guc set -Z datanode -D $datanode_dir -c "replication_type = 1" + gs_guc set -Z datanode -D $datanode_dir -c "enable_data_replicate = off" + gs_guc set -Z datanode -D $datanode_dir -c "available_zone = 'az1'" + gs_guc set -Z datanode -D $datanode_dir -c "enable_incremental_checkpoint = off" +done + +#python $scripts_dir/pgxc_multi.py -o + +sleep 2 +#start the database +python $scripts_dir/pgxc_multi_cascade_mot.py -s diff --git a/src/test/ha/ha_schedule_multi_cascade_mot b/src/test/ha/ha_schedule_multi_cascade_mot new file mode 100644 index 000000000..8726379be --- /dev/null +++ b/src/test/ha/ha_schedule_multi_cascade_mot @@ -0,0 +1,4 @@ +cascade/failover_mot +cascade/failover_with_data_mot +cascade/switchover_mot +cascade/inc_build_failover_mot diff --git a/src/test/ha/pgxc_multi_cascade_mot.py b/src/test/ha/pgxc_multi_cascade_mot.py new file mode 100644 index 000000000..428b50ea3 --- /dev/null +++ b/src/test/ha/pgxc_multi_cascade_mot.py @@ -0,0 +1,413 @@ +#!/usr/bin/python + +import getopt, sys, os +import shutil +import time +import string + +g_base_port = int(os.environ.get("g_base_port")) +g_pooler_base_port = int(os.environ.get("g_pooler_base_port")) +g_base_standby_port = int(os.environ.get("g_base_standby_port")) +install_path = os.environ.get("install_path") +g_data_path = os.environ.get("g_data_path") +g_local_ip = os.environ.get("g_local_ip") +g_file_node_info = "cndn.cnf" +g_valgrind = "" +g_passwd = "Gauss@123" +g_trace_compress = False + + +class Pterodb(): + def __init__(self, coordinator_num, data_node_num, data_dir): + self.coordinator_num = coordinator_num + self.data_node_num = data_node_num + self.data_dir = data_dir + self.dname_prefix = "datanode" + print self.data_dir + self.ha_port_arr = [0 for i in range(data_node_num+1)] + self.service_port_arr = [0 for i in range(data_node_num+1)] + self.heartbeat_port_arr = [0 for i in range(data_node_num+1)] + + def init_env(self): + if(os.path.exists(self.data_dir) == False): + os.mkdir(self.data_dir) + else: + shutil.rmtree(self.data_dir) + os.mkdir(self.data_dir) + print "rm dir ok" + + #generate port array + self.__generate_port() + + for i in range(1,self.data_node_num + 2): + if(i == 1): + #primary + datanode_cmd_init = install_path + "/bin/gs_initdb -D " + self.data_dir + "/" + self.dname_prefix + str(i) + " --nodename=" + self.dname_prefix + str(i) + " -w " + g_passwd + print datanode_cmd_init + os.system(datanode_cmd_init) + + #primary + conf_file = self.data_dir + "/" + self.dname_prefix + str(i) + "/postgresql.conf" + self.__modify_conf_port(conf_file,i+self.coordinator_num-1,1) + self.__turn_on_pg_log(conf_file) + self.__modify_conf_standby(conf_file,i,1) + self.__modify_conf_application_name(conf_file, "dn_p" + str(i)) + self.__modify_remote_read_mode(conf_file) + elif(i == 2): + #standby + datanode_cmd_standby = install_path + "/bin/gs_initdb -D " + self.data_dir + "/" + self.dname_prefix + str(i-1) + "_standby" + " --nodename=" + self.dname_prefix + str(1) + " -w " + g_passwd + print datanode_cmd_standby + os.system(datanode_cmd_standby) + #standby + conf_file_standby = self.data_dir + "/" + self.dname_prefix + str(i-1) + "_standby" + "/postgresql.conf" + self.__modify_conf_port(conf_file_standby,i+self.coordinator_num-1,i) + self.__turn_on_pg_log(conf_file_standby) + self.__modify_conf_standby(conf_file_standby,i,i) + self.__modify_conf_application_name(conf_file_standby, "dn_s" + str(i - 1)) + self.__modify_remote_read_mode(conf_file_standby) + else: + #cascade standby + datanode_cmd_standby = install_path + "/bin/gs_initdb -D " + self.data_dir + "/" + self.dname_prefix + str(i-1) + "_standby" + " --nodename=" + self.dname_prefix + str(1) + " -w " + g_passwd + print datanode_cmd_standby + os.system(datanode_cmd_standby) + #cascade standby + conf_file_standby = self.data_dir + "/" + self.dname_prefix + str(i-1) + "_standby" + "/postgresql.conf" + self.__modify_conf_port(conf_file_standby,i+self.coordinator_num-1,i) + self.__turn_on_pg_log(conf_file_standby) + self.__modify_conf_standby(conf_file_standby,i,i) + self.__modify_conf_application_name(conf_file_standby, "dn_c" + str(i - 1)) + self.__modify_remote_read_mode(conf_file_standby) + + + def __generate_port(self): + port = g_base_standby_port + for i in range(0,self.data_node_num+1): + self.ha_port_arr[i] = port + 1; + self.service_port_arr[i] = port + 2; + self.heartbeat_port_arr[i] = port + 3; + port = port + 3; + + def __modify_conf_standby(self, conf_file, n, flag): + j = 1 + + file_handler = open(conf_file,"a") + string = "listen_addresses = '*'"+ "\n" + file_handler.write(string) + + for i in range(1,self.data_node_num + 2): + if(i != n): + #repl + string = "replconninfo%d = 'localhost=%s localport=%d localheartbeatport=%d localservice=%d remotehost=%s remoteport=%d remoteheartbeatport=%d remoteservice=%d'\n" % \ + (j, g_local_ip, self.ha_port_arr[n-1], self.heartbeat_port_arr[n-1], self.service_port_arr[n-1], g_local_ip, self.ha_port_arr[i-1], self.heartbeat_port_arr[i-1], self.service_port_arr[i-1]) + print string + file_handler.write(string) + j = j + 1 + + file_handler.close() + + def __modify_conf_application_name(self, conf_file, name): + file_handler = open(conf_file,"a") + string = "application_name = '" + name + "'" + "\n" + file_handler.write(string) + file_handler.close() + + def __modify_remote_read_mode(self, conf_file): + file_handler = open(conf_file,"a") + string = "remote_read_mode = 'off'" + "\n" + file_handler.write(string) + file_handler.close() + + def __modify_conf_port(self, conf_file, n, role_flag): + file_handler = open(conf_file,"a") + port = g_base_port + 3 * n + + string = "port = " + str(port) + "\n" + file_handler.write(string) + file_handler.close() + + def __turn_on_pg_log(self, conf_file): + file_handler = open(conf_file,"a") + pglog_conf = "logging_collector = on \n" + pglog_conf = pglog_conf + "log_directory = 'pg_log' \n" + pglog_conf = pglog_conf + "log_line_prefix = '%m %c %d %p %a %x %e ' \n" + pglog_conf = pglog_conf + "enable_data_replicate = off \n" + pglog_conf = pglog_conf + "replication_type = 1 \n" + file_handler.write(pglog_conf) + file_handler.close() + + def __switch_trace_cmpr(self): + for i in range(1, self.data_node_num+1): + conf_file = self.data_dir + "/" + self.dname_prefix + str(i) + "/postgresql.conf" + file_handler = open(conf_file,"a") + if g_trace_compress: + pglog_conf = "log_line_prefix = '' \n" + pglog_conf = pglog_conf + "log_min_messages = info \n" + file_handler.write(pglog_conf) + else: + pglog_conf = "log_line_prefix = '%m %c %d %p %a %x %e ' \n" + pglog_conf = pglog_conf + "log_min_messages = warning \n" + file_handler.write(pglog_conf) + file_handler.close() + + def __create_regress_group(self): + create_regress_group = self.data_dir + "/create_regress_group.sql" + #the default group is group1 + sql = "CREATE NODE GROUP group1 WITH (" + for i in range(1,2): + sql = sql + self.dname_prefix +str(i)+ "," + sql = sql[:-1] + sql = sql + ");" + file_handler = open(create_regress_group, "w") + file_handler.write(sql) + file_handler.close() + time.sleep(2) + # execute create group sql + port = g_base_port + for i in range(0, self.coordinator_num): + cmd = install_path + "/bin/gsql -p " + str(port + 3 * i) + " postgres < " + create_regress_group + print cmd + os.system(cmd) + #return create_regress_group + + def __create_node(self): + create_nodes_sql = self.data_dir + "/create_nodes.sql" + Sql = "delete from pgxc_node;\n" + for i in range(1,self.coordinator_num+1): + port = g_base_port + 3* (i - 1) + Sql = Sql + "CREATE NODE coordinator" + str(i) + " WITH (HOST = 'localhost', type = 'coordinator', PORT = " + str(port) + ");\n" + + port = g_base_port + 3 * self.coordinator_num + for i in range(1,self.data_node_num+2): + #Sql = Sql + "CREATE NODE " + self.dname_prefix +str(i)+ " WITH (type = 'datanode', HOST = 'localhost', PORT = " + str(port) + ",sctp_port = " + str(g_base_port+(i+self.coordinator_num-1)*128) + ", control_port = " + str(port+1) + ", host1 = 'localhost', port1= "+str(port+2) + ", port2= " + str(port+4) + ", sctp_port = " + str(g_base_port+(i+self.coordinator_num-1)*128+64) + ", control_port = " + str(port+3) + ");\n" + if(i == 1): + Sql = Sql + "CREATE NODE " + self.dname_prefix + str(1) + " WITH (type = 'datanode', RW = 'true', HOST = 'localhost', PORT = " + str(port + (i-1)*3) + ",sctp_port = " + str(g_base_port+(i+self.coordinator_num-1)*128) + ", control_port = " + str(port + (i-1)*3 + 2) + ");\n" + else: + Sql = Sql + "CREATE NODE " + self.dname_prefix + str(1) + " WITH (type = 'datanode', RW = 'false', HOST = 'localhost', PORT = " + str(port +(i-1)*3) + ",sctp_port = " + str(g_base_port+(i+self.coordinator_num-1)*128) + ", control_port = " + str(port + (i-1)*3 + 2) + ");\n" + file_handler = open(create_nodes_sql, "w") + file_handler.write(Sql) + file_handler.close() + time.sleep(2) + #execute create node sql + for i in range(1,self.coordinator_num+1): + port = g_base_port + 3* (i - 1) + cmd = install_path + "/bin/gsql -p " + str(port) + " postgres < " + create_nodes_sql + print cmd + os.system(cmd) + + def __create_default_db(self): + # connect to primary DN to create db + cmd = install_path + "/bin/gsql -p " + str(g_base_port + 3) + " postgres -c 'create database test'" + os.system(cmd) + + def __rm_pid_file(self): + cmd = "rm -rf " + # dn + for i in range(1,self.data_node_num+2): + if(i == 1): + rm_cmd = cmd + self.data_dir + "/" + self.dname_prefix + str(i) + "/postmaster.pid" + print rm_cmd + os.system(rm_cmd) + else: + rm_cmd = cmd + self.data_dir + "/" + self.dname_prefix + str(i-1) + "_standby" +"/postmaster.pid" + print rm_cmd + os.system(rm_cmd) + + + #save coor_num and datanode num + def __save_nodes_info(self): + file_nodes_info = open(g_file_node_info,"w") + file_nodes_info.write(str(self.coordinator_num)) + file_nodes_info.write("\n") + file_nodes_info.write(str(self.data_node_num)) + file_nodes_info.write("\n") + file_nodes_info.write(str(self.data_dir)) + file_nodes_info.write("\n") + file_nodes_info.close() + + #read coor_num and datanode num + def __read_nodes_info(self): + file_nodes_info = open(g_file_node_info,"r") + lines = file_nodes_info.readlines() + self.coordinator_num = int (lines[0].strip()) + self.data_node_num = int (lines[1].strip()) + self.data_dir = lines[2].strip() + + file_nodes_info.close() + + def __start_server(self): + #clean evn + self.__rm_pid_file() + + #start data_node + for i in range(1,self.data_node_num+1): + datanode_cmd = g_valgrind + install_path + "/bin/gaussdb --single_node" + " -M pending" + " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + " > " + self.data_dir + "/" + self.dname_prefix + str(i) + "/logdn" + str(i) + ".log 2>&1 &" + time.sleep(2) + #datanode_cmd = g_valgrind + install_path + "bin/gaussdb --datanode" + " -M primary" + " -p " +str(port) + " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + #" &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + + datanode_cmd = g_valgrind + install_path + "/bin/gs_ctl " + "notify -M primary" + " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + " > " + self.data_dir + "/" + self.dname_prefix + str(i) + "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + break; + + time.sleep(5) + + #start data_node_standby1,2,3...7 + for i in range(1,self.data_node_num+1): + datanode_cmd = g_valgrind + install_path + "/bin/gaussdb --single_node" + " -M pending "+ " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " > " + self.data_dir + "/" + self.dname_prefix + str(i) +"_standby"+ "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + + datanode_cmd = g_valgrind + install_path + "/bin/gs_ctl" + " notify -M standby "+ " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " > " + self.data_dir + "/" + self.dname_prefix + str(i) +"_standby"+ "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + + datanode_cmd = g_valgrind + install_path + "/bin/gs_ctl" + " build "+ "-D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " -Z single_node " + " > " + self.data_dir + "/" + self.dname_prefix + str(i) +"_standby"+ "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + break; + + time.sleep(5) + + #start data_node_cascade_standby1,2,3...7 + for i in range(2,self.data_node_num+1): + datanode_cmd = g_valgrind + install_path + "/bin/gaussdb --single_node" + " -M cascade_standby "+ " -D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " > " + self.data_dir + "/" + self.dname_prefix + str(i) +"_standby"+ "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + + datanode_cmd = g_valgrind + install_path + "/bin/gs_ctl" + " build -M cascade_standby "+ "-D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " -Z single_node " + " > " + self.data_dir + "/" + self.dname_prefix + str(i) +"_standby"+ "/logdn" + str(i) + ".log 2>&1 &" + print datanode_cmd + os.system(datanode_cmd) + time.sleep(5) + + time.sleep(5) + + + def __stop_server(self): + #stop data node + for i in range(1,self.data_node_num+1): + datanode_cmd = install_path + "/bin/gs_ctl stop -D " + self.data_dir + "/" + self.dname_prefix + str(i) + " -Z single_node" + print datanode_cmd + os.system(datanode_cmd) + break + #stop data node standby1,2,3...7 + for i in range(1,self.data_node_num+1): + datanode_cmd = install_path + "/bin/gs_ctl stop -D " + self.data_dir + "/" + self.dname_prefix + str(i) + "_standby" + " -Z single_node" + print datanode_cmd + os.system(datanode_cmd) + + def run(self, run_type): + #self.kill_process() + if(run_type == 0): + self.init_env() + #print "init_env ok" + self.__save_nodes_info() + #print "save_nodes_info ok" + self.__read_nodes_info() + #print "read_nodes_info ok" + self.__start_server() + #print "start_server ok" + #self.__create_node() + #print "create_node ok" + #self.__create_regress_group() + #print "create_regress_group ok" + self.__create_default_db() + #print "create_default_db ok" + print "start ok" + elif(run_type == 1): + self.__read_nodes_info() + self.__start_server() + print "start ok" + elif(run_type == 2): + self.__read_nodes_info() + self.__stop_server() + print "stop ok" + elif (run_type == 3): + self.__read_nodes_info() + self.__switch_trace_cmpr() + print "compress trace changed" + elif (run_type == 4): + self.__read_nodes_info() + #filepath = self.__create_regress_group() + print filepath + +def usage(): + print "------------------------------------------------------" + print "python pgxc.py\n" + print " -c coor_num -d datanode_num, set and start up cn/dn" + print " -t trace compression log" + print " -s means start" + print " -o means stop" + print " -g means memcheck" + print " -D data directory" + print " -r create regression group sql" + print "------------------------------------------------------" + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "hrD:c:d:t:sovg", ["help", "data_dir=", "regress="]) + except getopt.GetoptError, err: + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + # usage() + sys.exit(2) + + coordinator_num = 0 + datanode_num = 0 + global g_valgrind; + global g_file_node_info; + global g_trace_compress; + + data_dir = g_data_path + #1 start + #2 stop + run_type = 0 + + for o, a in opts: + if o == "-v": + verbose = True + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-D", "data_dir"): + data_dir = a + elif o in ("-c", "--coordinator"): + coordinator_num = int(a) + elif o in ("-d", "--datanode"): + datanode_num = int(a) + elif o in ("-s", "--start"): + run_type = 1 + elif o in ("-o", "--stop"): + run_type = 2 + elif o in ("-g", "--memcheck"): + g_valgrind = "valgrind --tool=memcheck --leak-check=full --log-file=memcheck.log " + #g_valgrind = "valgrind --tool=massif --time-unit=B --detailed-freq=1 --massif-out-file=mass.out " + elif o in ("-t", "--trace"): + if 'on' == a: + g_trace_compress = True + else: + g_trace_compress = False + run_type = 3 + print g_trace_compress + elif o in ("-r", "--regress"): + run_type = 4 + else: + assert False, "unhandled option" + + if((datanode_num == 0) and run_type == 0): + usage() + sys.exit() + + g_file_node_info = data_dir + "/" + g_file_node_info; + ptdb = Pterodb(coordinator_num,datanode_num, data_dir) + ptdb.run(run_type) + + +if __name__ == "__main__": + main() diff --git a/src/test/ha/run_ha_multi_cascade_mot.sh b/src/test/ha/run_ha_multi_cascade_mot.sh new file mode 100644 index 000000000..d0422141c --- /dev/null +++ b/src/test/ha/run_ha_multi_cascade_mot.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# run all the test case of ha + +#init some variables +loop_num=$1 +if [ -z $1 ]; then + loop_num=1 +fi +count=0 + +source ./standby_env.sh +test -f regression.diffs.hacheck.cascade_mot && rm regression.diffs.hacheck.cascade_mot + +total_starttime=`date +"%Y-%m-%d %H:%M:%S"` +total_startvalue=`date -d "$total_starttime" +%s` + +array=("cascade_standby_single") +for element in ${array[@]} +do + mkdir -vp ./results/$element +done + +#init and start the database +printf "init and start the database\n" +sh deploy_multi_cascade_mot.sh > ./results/deploy_standby_multi_cascade_mot.log 2>&1 + +for((i=1;i<=$loop_num;i++)) +do + printf "run the ha_schedule %d time\n" $i + printf "%-50s%-10s%-10s\n" "testcase" "result" "time(s)" + for line in `cat ha_schedule_multi_cascade_mot$2 | grep -v ^#` + do + printf "%-50s" $line + starttime=`date +"%Y-%m-%d %H:%M:%S"` + sh ./testcase/$line.sh > ./results/$line.log 2>&1 + count=`expr $count + 1` + endtime=`date +"%Y-%m-%d %H:%M:%S"` + starttime1=`date -d "$starttime" +%s` + endtime1=`date -d "$endtime" +%s` + interval=`expr $endtime1 - $starttime1` + if [ $( grep "$failed_keyword" ./results/$line.log | grep -v "the database system is shutting down" | wc -l ) -eq 0 ]; then + printf "%-10s%-10s\n" ".... ok" $interval + else + printf "%-10s%-10s\n" ".... FAILED" $interval + cp ./results/$line.log regression.diffs.hacheck.cascade_mot + exit 1 + fi + done +done + +#stop the database +printf "stop the database\n" +python $scripts_dir/pgxc_multi_cascade_mot.py -o > ./results/stop_database_multi_cascade_mot.log 2>&1 + +total_endtime=`date +"%Y-%m-%d %H:%M:%S"` +total_endvalue=`date -d "$total_endtime" +%s` +printf "all %d tests passed.\n" $count +printf "total time: %ss\n" $(($total_endvalue - $total_startvalue)) diff --git a/src/test/ha/testcase/cascade/failover_mot.sh b/src/test/ha/testcase/cascade/failover_mot.sh new file mode 100644 index 000000000..8470f5ca1 --- /dev/null +++ b/src/test/ha/testcase/cascade/failover_mot.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +source ./util.sh + +function test_1() +{ + set_cascade_default + check_instance_cascade_standby + cstore_rawdata_lines=8 + + echo "cluster setup completed----------------" + #create table + #gsql -d $db -p $dn1_primary_port -c "set enable_data_replicate=on; copy cstore_copy_t1 from '$scripts_dir/data/cstore_copy_t1.data' delimiter '|';" + #gsql -d $db -p $dn1_primary_port -c "DROP TABLE if exists cstore_copy_t1; create table cstore_copy_t1(c1 int2, c2 int4, c3 int8, c4 char(10), c5 varchar(12),c6 numeric(10,2)) with (orientation = column);" + gsql -d $db -p $dn1_primary_port -c "DROP foreign TABLE if exists cstore_copy_t1; create foreign table cstore_copy_t1(c1 int2, c2 int4, c3 int8, c4 char(10), c5 varchar(12),c6 numeric(10,2));" + + #copy data(25M) to primary + cat $scripts_dir'/data/cstore_copy_t1.data' | python tools.py cstore_copy_t1 '|' > $scripts_dir'/data/cstore_copy_t1.data.sql' + gsql -d $db -p $dn1_primary_port < $scripts_dir'/data/cstore_copy_t1.data.sql' + echo "copy success" + + echo "begin to kill primary" + #kill the primary_node_name + kill_primary + + #failover standby to primary + failover_to_standby + + echo "begin to query standby" + query_standby + + #test the copy results on dn1_standby + if [ $(gsql -d $db -p $dn1_standby_port -c "select pgxc_pool_reload();select count(1) from cstore_copy_t1;" | grep `expr 1 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby cstore_copy_t1" + else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 + fi + + #failover cascade_standby to standby + failover_to_cascade_standby + + echo "begin to query cascade_standby" + query_cascade_standby + + #test the copy results on dn1_standby + if [ $(gsql -d $db -p $standby2_port -c "select pgxc_pool_reload();select count(1) from cstore_copy_t1;" | grep `expr 1 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on cascade_standby cstore_copy_t1" + else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 + fi + + + + + start_primary_as_cascade_standby + sleep 5 + switchover_to_primary + + #test the copy results on dn1_primary + if [ $(gsql -d $db -p $dn1_primary_port -c "select pgxc_pool_reload();select count(1) from cstore_copy_t1;" | grep `expr 1 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_primary cstore_copy_t1" + else + echo "copy $failed_keyword on dn1_primary cstore_copy_t1" + exit 1 + fi + + #test the copy results on cascade_standby + if [ $(gsql -d $db -p $standby2_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 1 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on cascade_standby cstore_copy_t1" + else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 + fi +} + +function tear_down() +{ + set_cascade_default + sleep 1 + gsql -d $db -p $dn1_primary_port -c "DROP foreign TABLE if exists cstore_copy_t1;" +} + +test_1 +tear_down diff --git a/src/test/ha/testcase/cascade/failover_with_data_mot.sh b/src/test/ha/testcase/cascade/failover_with_data_mot.sh new file mode 100644 index 000000000..b8ae6e5fc --- /dev/null +++ b/src/test/ha/testcase/cascade/failover_with_data_mot.sh @@ -0,0 +1,127 @@ +#!/bin/sh + +source ./util.sh + +function test_1() +{ +set_cascade_default +check_instance_cascade_standby +cstore_rawdata_lines=8 + +query_primary +#create table +gsql -d $db -p $dn1_primary_port -c "DROP TABLE if exists cstore_copy_t1; create table cstore_copy_t1(c1 int2, c2 int4, c3 int8, c4 char(10), c5 varchar(12),c6 numeric(10,2));" + +gsql -d $db -p $dn1_primary_port -c "copy cstore_copy_t1 from '$scripts_dir/data/cstore_copy_t1.data' delimiter '|';" + +#test the copy results on dn1_primary +if [ $(gsql -d $db -p $dn1_primary_port -c "select count(1) from cstore_copy_t1;" | grep `expr 1 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_primary cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_primary cstore_copy_t1" + exit 1 +fi + + +#copy data(25M) to primary +gsql -d $db -p $dn1_primary_port -c "copy cstore_copy_t1 from '$scripts_dir/data/cstore_copy_t1.data' delimiter '|';" + + +#kill the primary_node_name +kill_primary +echo "kill primary success" +sleep 10 + +echo `date` + +#test the copy results on dn1_standby +if [ $(gsql -d $db -p $dn1_standby_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 +fi + +#test the copy results on cascade standby +if [ $(gsql -d $db -p $dn1_standby_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on cascade standby cstore_copy_t1" +else + echo "copy $failed_keyword on cascade standby cstore_copy_t1" + exit 1 +fi + + +echo "begin to failover to standby2" +failover_to_standby +echo "failover to standby2 success" + +echo `date` + +sleep 5 + +echo "query standby2 status" +query_standby + +sleep 2 + +echo `date` + +failover_to_cascade_standby +#test the copy results on dn1_standby +if [ $(gsql -d $db -p $dn1_standby_port -m -c "select pgxc_pool_reload();select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 +fi + +#test the copy results on cascade standby +if [ $(gsql -d $db -p $dn1_standby_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on cascade standby cstore_copy_t1" +else + echo "copy $failed_keyword on cascade standby cstore_copy_t1" + exit 1 +fi + + + + +#test the copy results on dn1_standby +if [ $(gsql -d $db -p $dn1_standby_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 +fi + +start_primary_as_cascade_standby +sleep 10 +#gs_ctl switchover -w -t $gsctl_wait_time -D $data_dir/datanode1 +switchover_to_primary + +#test the copy results on dn1_primary +if [ $(gsql -d $db -p $dn1_primary_port -c "select pgxc_pool_reload();select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_primary cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_primary cstore_copy_t1" + exit 1 +fi + +#test the copy results on dn1_standby +if [ $(gsql -d $db -p $standby2_port -m -c "select count(1) from cstore_copy_t1;" | grep `expr 2 \* $cstore_rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby cstore_copy_t1" +else + echo "copy $failed_keyword on dn1_standby cstore_copy_t1" + exit 1 +fi +} + +function tear_down() +{ + set_cascade_default + sleep 1 + gsql -d $db -p $dn1_primary_port -c "DROP TABLE if exists cstore_copy_t1;" +} + +test_1 +tear_down diff --git a/src/test/ha/testcase/cascade/inc_build_failover_mot.sh b/src/test/ha/testcase/cascade/inc_build_failover_mot.sh new file mode 100644 index 000000000..842622fb3 --- /dev/null +++ b/src/test/ha/testcase/cascade/inc_build_failover_mot.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# 1 old primary down +# 2 new primary is working +# 3 old primary start as standby ,then should inc build not full build; +source ./util.sh + +function test_1() +{ + set_cascade_default + kill_cascade_cluster + start_cascade_cluster + + echo "start cluter success!" + + gsql -d $db -p $dn1_primary_port -c "DROP FOREIGN TABLE if exists mot_switch1; CREATE FOREIGN TABLE mot_switch1(id INT,name VARCHAR(15) NOT NULL) SERVER mot_server;" + gsql -d $db -p $dn1_primary_port -c "copy mot_switch1 from '$scripts_dir/data/data5';" + + echo "mot data loaded" + + inc_build_pattern="dn incremental build completed" + kill_primary + echo "primary killed" + failover_to_standby + echo "failover_to_standby" + build_result=`gs_ctl build -Z single_node -D ${standby2_data_dir} -M cascade_standby` + if [[ $build_result =~ $inc_build_pattern ]] + then + echo "inc build success" + else + echo "inc build $failed_keyword" + fi + + failover_to_cascade_standby + sleep 1 + kill_standby + sleep 1 + failover_to_cascade_standby + build_result=`gs_ctl build -Z single_node -D ${standby_data_dir} -M cascade_standby` + if [[ $build_result =~ $inc_build_pattern ]] + then + echo "inc build success" + else + echo "inc build $failed_keyword" + fi + build_result=`gs_ctl build -Z single_node -D ${primary_data_dir}` + if [[ $build_result =~ $inc_build_pattern ]] + then + echo "inc build success" + else + echo "inc build $failed_keyword" + fi + + sleep 30 + gsql -d $db -p $dn1_primary_port -m -c "select count(1) from mot_switch1;" + print_time + if [ $(gsql -d $db -p $dn1_primary_port -m -c "select count(1) from mot_switch1;" | grep `expr 1 \* $rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_primary" + else + echo "copy $failed_keyword on dn1_primary" + fi + + gsql -d $db -p $dn1_standby_port -m -c "select count(1) from mot_switch1;" + print_time + if [ $(gsql -d $db -p $dn1_standby_port -m -c "select count(1) from mot_switch1;" | grep `expr 1 \* $rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on dn1_standby" + else + echo "copy $failed_keyword on dn1_standby" + fi + + gsql -d $db -p $standby2_port -m -c "select count(1) from mot_switch1;" + print_time + if [ $(gsql -d $db -p $standby2_port -m -c "select count(1) from mot_switch1;" | grep `expr 1 \* $rawdata_lines` |wc -l) -eq 1 ]; then + echo "copy success on cascade standby" + else + echo "copy $failed_keyword on cascade standby" + fi + +} + +function tear_down() { + sleep 1 + failover_to_primary + sleep 1 + failover_to_standby + gsql -d $db -p $dn1_standby_port -m -c " select * from pg_drop_replication_slot('dn_c2');" + gsql -d $db -p $dn1_standby_port -m -c " select * from pg_drop_replication_slot('dn_p1');" +} + +test_1 +tear_down diff --git a/src/test/ha/testcase/cascade/switchover_mot.sh b/src/test/ha/testcase/cascade/switchover_mot.sh new file mode 100644 index 000000000..1b390207d --- /dev/null +++ b/src/test/ha/testcase/cascade/switchover_mot.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# switchover when primary-standby-dummystandby all ready + +source ./util.sh + +function test_1() +{ + set_cascade_default + check_instance_cascade_standby + check_cascade_detailed_instance + #create table + gsql -d $db -p $dn1_primary_port -c "DROP FOREIGN TABLE if exists mpp_test1; CREATE foreign TABLE mpp_test1(id INT,name VARCHAR(15) NOT NULL);" + echo "drop table success" + + #copy error, use insert to by-pass + #copy data(25M) to standby 1 times + #gsql -d $db -p $dn1_primary_port -c "set enable_data_replicate=on; copy mpp_test1 from '$scripts_dir/data/data5';" + + #prepare insert sql + cat $scripts_dir'/data/data5_head_100' | python tools.py mpp_test1 '|' > $scripts_dir'/data/data5_head_100_sql' + gsql -d $db -p $dn1_primary_port < $scripts_dir'/data/data5_head_100_sql' &> /dev/null + + #test the insert results + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + echo "b=" $b + if [ $(gsql -d $db -p $dn1_primary_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "insert into table success!" + else + echo "insert into table failure $failed_keyword!" + exit 1 + fi + + sleep 10 + + + #test the insert results primary + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_primary_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + #test the insert results standby + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_standby_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + #test the insert results cascade standby + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $standby2_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + echo "begin to switch to cascade standby" + #switchover + switchover_to_cascade_standby + echo "end of swtich to cascade standby" + + #test the insert results primary + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_primary_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + #test the insert results standby + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_standby_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + + #test the insert results + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $standby2_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + sleep 10 + + echo "begin to switch to cascade standby" + #switchover + switchover_to_cascade_standby + echo "end of swtich to cascade standby" + + sleep 10 + + #echo "begin to switch to standby4" + #switchover + #switchover_to_standby4 + #echo "end of swtich to standby4" + + #test the insert results primary + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_primary_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + #test the insert results standby + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $dn1_standby_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi + + + #test the insert results + b=`wc $scripts_dir'/data/data5_head_100_sql' | awk '{print $1}'` + if [ $(gsql -d $db -p $standby2_port -c "select count(1) from mpp_test1;" | grep $b | wc -l) -eq 1 ]; then + echo "test insert result success!" + else + echo "test insert result, $failed_keyword!" + exit 1 + fi +} + +function tear_down() +{ + set_cascade_default + sleep 3 + gsql -d $db -p $dn1_primary_port -c "DROP FOREIGN TABLE if exists mpp_test1;" +} + +test_1 +tear_down diff --git a/src/test/regress/expected/mot/single_alter_foreign_table.out b/src/test/regress/expected/mot/single_alter_foreign_table.out index d5c8d0b0a..831f62849 100644 --- a/src/test/regress/expected/mot/single_alter_foreign_table.out +++ b/src/test/regress/expected/mot/single_alter_foreign_table.out @@ -12,7 +12,8 @@ SELECT * FROM T1; (1 row) ALTER TABLE T1 ADD COLUMN STR1 varchar(80); -ERROR: "t1" is a mot, which does not support alter table. +ERROR: "t1" is not a table +HINT: Use ALTER FOREIGN TABLE to alter a foreign table. SELECT * FROM T1; string ---------------------------------------------------------------- diff --git a/src/test/regress/expected/mot/single_float8.out b/src/test/regress/expected/mot/single_float8.out index 0da6aef82..47622117c 100644 --- a/src/test/regress/expected/mot/single_float8.out +++ b/src/test/regress/expected/mot/single_float8.out @@ -395,7 +395,7 @@ SELECT '' AS five, * FROM FLOAT8_TBL ORDER BY f1; UPDATE FLOAT8_TBL SET f1 = FLOAT8_TBL.f1 * '-1' WHERE FLOAT8_TBL.f1 > '0.0'; -ERROR: Update of indexed column is not supported for memory table +ERROR: Update of primary key column is not supported for memory table SELECT '' AS bad, f.f1 * '1e200' from FLOAT8_TBL f ORDER BY f1; ERROR: value out of range: overflow diff --git a/src/test/regress/expected/mot/single_join_cross_engine_check.out b/src/test/regress/expected/mot/single_join_cross_engine_check.out index e7bdbe3b6..f5d2868c4 100644 --- a/src/test/regress/expected/mot/single_join_cross_engine_check.out +++ b/src/test/regress/expected/mot/single_join_cross_engine_check.out @@ -115,12 +115,124 @@ SELECT '' AS "xxx", * SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) ORDER BY a, b, c, d, e; -ERROR: Cross storage engine query is not supported + xxx | a | b | c | d | e +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 0 | | zero | 1 | -1 + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | 3 | -3 + | 0 | | zero | 5 | -5 + | 0 | | zero | 5 | -5 + | 0 | | zero | | 0 + | 0 | | zero | | + | 1 | 4 | one | 0 | + | 1 | 4 | one | 1 | -1 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 1 | 4 | one | 3 | -3 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | | 0 + | 1 | 4 | one | | + | 2 | 3 | two | 0 | + | 2 | 3 | two | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 2 | 3 | two | 3 | -3 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | | 0 + | 2 | 3 | two | | + | 3 | 2 | three | 0 | + | 3 | 2 | three | 1 | -1 + | 3 | 2 | three | 2 | 2 + | 3 | 2 | three | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | | 0 + | 3 | 2 | three | | + | 4 | 1 | four | 0 | + | 4 | 1 | four | 1 | -1 + | 4 | 1 | four | 2 | 2 + | 4 | 1 | four | 2 | 4 + | 4 | 1 | four | 3 | -3 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | | 0 + | 4 | 1 | four | | + | 5 | 0 | five | 0 | + | 5 | 0 | five | 1 | -1 + | 5 | 0 | five | 2 | 2 + | 5 | 0 | five | 2 | 4 + | 5 | 0 | five | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | | 0 + | 5 | 0 | five | | + | 6 | 6 | six | 0 | + | 6 | 6 | six | 1 | -1 + | 6 | 6 | six | 2 | 2 + | 6 | 6 | six | 2 | 4 + | 6 | 6 | six | 3 | -3 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | | 0 + | 6 | 6 | six | | + | 7 | 7 | seven | 0 | + | 7 | 7 | seven | 1 | -1 + | 7 | 7 | seven | 2 | 2 + | 7 | 7 | seven | 2 | 4 + | 7 | 7 | seven | 3 | -3 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | | 0 + | 7 | 7 | seven | | + | 8 | 8 | eight | 0 | + | 8 | 8 | eight | 1 | -1 + | 8 | 8 | eight | 2 | 2 + | 8 | 8 | eight | 2 | 4 + | 8 | 8 | eight | 3 | -3 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | | 0 + | 8 | 8 | eight | | + | | 0 | zero | 0 | + | | 0 | zero | 1 | -1 + | | 0 | zero | 2 | 2 + | | 0 | zero | 2 | 4 + | | 0 | zero | 3 | -3 + | | 0 | zero | 5 | -5 + | | 0 | zero | 5 | -5 + | | 0 | zero | | 0 + | | 0 | zero | | + | | | null | 0 | + | | | null | 1 | -1 + | | | null | 2 | 2 + | | | null | 2 | 4 + | | | null | 3 | -3 + | | | null | 5 | -5 + | | | null | 5 | -5 + | | | null | | 0 + | | | null | | +(99 rows) + SELECT '' AS "xxx", t1.a, t2.e FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) WHERE t1.a = t2.d ORDER BY a, e; -ERROR: Cross storage engine query is not supported + xxx | a | e +-----+---+---- + | 0 | + | 1 | -1 + | 2 | 2 + | 2 | 4 + | 3 | -3 + | 5 | -5 + | 5 | -5 +(7 rows) + -- -- CROSS JOIN -- Qualifications are not allowed on cross joins, @@ -129,7 +241,109 @@ ERROR: Cross storage engine query is not supported SELECT '' AS "xxx", * FROM J1_TBL CROSS JOIN J2_TBL ORDER BY J1_TBL.i, J1_TBL.j, J1_TBL.t, J2_TBL.i, J2_TBL.k; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | i | k +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 0 | | zero | 1 | -1 + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | 3 | -3 + | 0 | | zero | 5 | -5 + | 0 | | zero | 5 | -5 + | 0 | | zero | | 0 + | 0 | | zero | | + | 1 | 4 | one | 0 | + | 1 | 4 | one | 1 | -1 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 1 | 4 | one | 3 | -3 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | | 0 + | 1 | 4 | one | | + | 2 | 3 | two | 0 | + | 2 | 3 | two | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 2 | 3 | two | 3 | -3 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | | 0 + | 2 | 3 | two | | + | 3 | 2 | three | 0 | + | 3 | 2 | three | 1 | -1 + | 3 | 2 | three | 2 | 2 + | 3 | 2 | three | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | | 0 + | 3 | 2 | three | | + | 4 | 1 | four | 0 | + | 4 | 1 | four | 1 | -1 + | 4 | 1 | four | 2 | 2 + | 4 | 1 | four | 2 | 4 + | 4 | 1 | four | 3 | -3 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | | 0 + | 4 | 1 | four | | + | 5 | 0 | five | 0 | + | 5 | 0 | five | 1 | -1 + | 5 | 0 | five | 2 | 2 + | 5 | 0 | five | 2 | 4 + | 5 | 0 | five | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | | 0 + | 5 | 0 | five | | + | 6 | 6 | six | 0 | + | 6 | 6 | six | 1 | -1 + | 6 | 6 | six | 2 | 2 + | 6 | 6 | six | 2 | 4 + | 6 | 6 | six | 3 | -3 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | | 0 + | 6 | 6 | six | | + | 7 | 7 | seven | 0 | + | 7 | 7 | seven | 1 | -1 + | 7 | 7 | seven | 2 | 2 + | 7 | 7 | seven | 2 | 4 + | 7 | 7 | seven | 3 | -3 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | | 0 + | 7 | 7 | seven | | + | 8 | 8 | eight | 0 | + | 8 | 8 | eight | 1 | -1 + | 8 | 8 | eight | 2 | 2 + | 8 | 8 | eight | 2 | 4 + | 8 | 8 | eight | 3 | -3 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | | 0 + | 8 | 8 | eight | | + | | 0 | zero | 0 | + | | 0 | zero | 1 | -1 + | | 0 | zero | 2 | 2 + | | 0 | zero | 2 | 4 + | | 0 | zero | 3 | -3 + | | 0 | zero | 5 | -5 + | | 0 | zero | 5 | -5 + | | 0 | zero | | 0 + | | 0 | zero | | + | | | null | 0 | + | | | null | 1 | -1 + | | | null | 2 | 2 + | | | null | 2 | 4 + | | | null | 3 | -3 + | | | null | 5 | -5 + | | | null | 5 | -5 + | | | null | | 0 + | | | null | | +(99 rows) + -- ambiguous column SELECT '' AS "xxx", i, k, t FROM J1_TBL CROSS JOIN J2_TBL; @@ -141,21 +355,1221 @@ CONTEXT: referenced column: i SELECT '' AS "xxx", t1.i, k, t FROM J1_TBL t1 CROSS JOIN J2_TBL t2 ORDER BY i, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | k | t +-----+---+----+------- + | 0 | -5 | zero + | 0 | -5 | zero + | 0 | -3 | zero + | 0 | -1 | zero + | 0 | 0 | zero + | 0 | 2 | zero + | 0 | 4 | zero + | 0 | | zero + | 0 | | zero + | 1 | -5 | one + | 1 | -5 | one + | 1 | -3 | one + | 1 | -1 | one + | 1 | 0 | one + | 1 | 2 | one + | 1 | 4 | one + | 1 | | one + | 1 | | one + | 2 | -5 | two + | 2 | -5 | two + | 2 | -3 | two + | 2 | -1 | two + | 2 | 0 | two + | 2 | 2 | two + | 2 | 4 | two + | 2 | | two + | 2 | | two + | 3 | -5 | three + | 3 | -5 | three + | 3 | -3 | three + | 3 | -1 | three + | 3 | 0 | three + | 3 | 2 | three + | 3 | 4 | three + | 3 | | three + | 3 | | three + | 4 | -5 | four + | 4 | -5 | four + | 4 | -3 | four + | 4 | -1 | four + | 4 | 0 | four + | 4 | 2 | four + | 4 | 4 | four + | 4 | | four + | 4 | | four + | 5 | -5 | five + | 5 | -5 | five + | 5 | -3 | five + | 5 | -1 | five + | 5 | 0 | five + | 5 | 2 | five + | 5 | 4 | five + | 5 | | five + | 5 | | five + | 6 | -5 | six + | 6 | -5 | six + | 6 | -3 | six + | 6 | -1 | six + | 6 | 0 | six + | 6 | 2 | six + | 6 | 4 | six + | 6 | | six + | 6 | | six + | 7 | -5 | seven + | 7 | -5 | seven + | 7 | -3 | seven + | 7 | -1 | seven + | 7 | 0 | seven + | 7 | 2 | seven + | 7 | 4 | seven + | 7 | | seven + | 7 | | seven + | 8 | -5 | eight + | 8 | -5 | eight + | 8 | -3 | eight + | 8 | -1 | eight + | 8 | 0 | eight + | 8 | 2 | eight + | 8 | 4 | eight + | 8 | | eight + | 8 | | eight + | | -5 | null + | | -5 | null + | | -5 | zero + | | -5 | zero + | | -3 | null + | | -3 | zero + | | -1 | null + | | -1 | zero + | | 0 | null + | | 0 | zero + | | 2 | null + | | 2 | zero + | | 4 | null + | | 4 | zero + | | | null + | | | null + | | | zero + | | | zero +(99 rows) + SELECT '' AS "xxx", ii, tt, kk FROM (J1_TBL CROSS JOIN J2_TBL) AS tx (ii, jj, tt, ii2, kk) ORDER BY ii, tt, kk; -ERROR: Cross storage engine query is not supported + xxx | ii | tt | kk +-----+----+-------+---- + | 0 | zero | -5 + | 0 | zero | -5 + | 0 | zero | -3 + | 0 | zero | -1 + | 0 | zero | 0 + | 0 | zero | 2 + | 0 | zero | 4 + | 0 | zero | + | 0 | zero | + | 1 | one | -5 + | 1 | one | -5 + | 1 | one | -3 + | 1 | one | -1 + | 1 | one | 0 + | 1 | one | 2 + | 1 | one | 4 + | 1 | one | + | 1 | one | + | 2 | two | -5 + | 2 | two | -5 + | 2 | two | -3 + | 2 | two | -1 + | 2 | two | 0 + | 2 | two | 2 + | 2 | two | 4 + | 2 | two | + | 2 | two | + | 3 | three | -5 + | 3 | three | -5 + | 3 | three | -3 + | 3 | three | -1 + | 3 | three | 0 + | 3 | three | 2 + | 3 | three | 4 + | 3 | three | + | 3 | three | + | 4 | four | -5 + | 4 | four | -5 + | 4 | four | -3 + | 4 | four | -1 + | 4 | four | 0 + | 4 | four | 2 + | 4 | four | 4 + | 4 | four | + | 4 | four | + | 5 | five | -5 + | 5 | five | -5 + | 5 | five | -3 + | 5 | five | -1 + | 5 | five | 0 + | 5 | five | 2 + | 5 | five | 4 + | 5 | five | + | 5 | five | + | 6 | six | -5 + | 6 | six | -5 + | 6 | six | -3 + | 6 | six | -1 + | 6 | six | 0 + | 6 | six | 2 + | 6 | six | 4 + | 6 | six | + | 6 | six | + | 7 | seven | -5 + | 7 | seven | -5 + | 7 | seven | -3 + | 7 | seven | -1 + | 7 | seven | 0 + | 7 | seven | 2 + | 7 | seven | 4 + | 7 | seven | + | 7 | seven | + | 8 | eight | -5 + | 8 | eight | -5 + | 8 | eight | -3 + | 8 | eight | -1 + | 8 | eight | 0 + | 8 | eight | 2 + | 8 | eight | 4 + | 8 | eight | + | 8 | eight | + | | null | -5 + | | null | -5 + | | null | -3 + | | null | -1 + | | null | 0 + | | null | 2 + | | null | 4 + | | null | + | | null | + | | zero | -5 + | | zero | -5 + | | zero | -3 + | | zero | -1 + | | zero | 0 + | | zero | 2 + | | zero | 4 + | | zero | + | | zero | +(99 rows) + SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e)) AS tx (ii, jj, tt, ii2, kk) ORDER BY ii, jj, kk; -ERROR: Cross storage engine query is not supported + xxx | ii | jj | kk +-----+----+----+---- + | 0 | | -5 + | 0 | | -5 + | 0 | | -3 + | 0 | | -1 + | 0 | | 0 + | 0 | | 2 + | 0 | | 4 + | 0 | | + | 0 | | + | 1 | 4 | -5 + | 1 | 4 | -5 + | 1 | 4 | -3 + | 1 | 4 | -1 + | 1 | 4 | 0 + | 1 | 4 | 2 + | 1 | 4 | 4 + | 1 | 4 | + | 1 | 4 | + | 2 | 3 | -5 + | 2 | 3 | -5 + | 2 | 3 | -3 + | 2 | 3 | -1 + | 2 | 3 | 0 + | 2 | 3 | 2 + | 2 | 3 | 4 + | 2 | 3 | + | 2 | 3 | + | 3 | 2 | -5 + | 3 | 2 | -5 + | 3 | 2 | -3 + | 3 | 2 | -1 + | 3 | 2 | 0 + | 3 | 2 | 2 + | 3 | 2 | 4 + | 3 | 2 | + | 3 | 2 | + | 4 | 1 | -5 + | 4 | 1 | -5 + | 4 | 1 | -3 + | 4 | 1 | -1 + | 4 | 1 | 0 + | 4 | 1 | 2 + | 4 | 1 | 4 + | 4 | 1 | + | 4 | 1 | + | 5 | 0 | -5 + | 5 | 0 | -5 + | 5 | 0 | -3 + | 5 | 0 | -1 + | 5 | 0 | 0 + | 5 | 0 | 2 + | 5 | 0 | 4 + | 5 | 0 | + | 5 | 0 | + | 6 | 6 | -5 + | 6 | 6 | -5 + | 6 | 6 | -3 + | 6 | 6 | -1 + | 6 | 6 | 0 + | 6 | 6 | 2 + | 6 | 6 | 4 + | 6 | 6 | + | 6 | 6 | + | 7 | 7 | -5 + | 7 | 7 | -5 + | 7 | 7 | -3 + | 7 | 7 | -1 + | 7 | 7 | 0 + | 7 | 7 | 2 + | 7 | 7 | 4 + | 7 | 7 | + | 7 | 7 | + | 8 | 8 | -5 + | 8 | 8 | -5 + | 8 | 8 | -3 + | 8 | 8 | -1 + | 8 | 8 | 0 + | 8 | 8 | 2 + | 8 | 8 | 4 + | 8 | 8 | + | 8 | 8 | + | | 0 | -5 + | | 0 | -5 + | | 0 | -3 + | | 0 | -1 + | | 0 | 0 + | | 0 | 2 + | | 0 | 4 + | | 0 | + | | 0 | + | | | -5 + | | | -5 + | | | -3 + | | | -1 + | | | 0 + | | | 2 + | | | 4 + | | | + | | | +(99 rows) + SELECT '' AS "xxx", * FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b ORDER BY J1_TBL.i,J1_TBL.j,J1_TBL.t,a.i,a.k,b.i,b.k; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | i | k | i | k +-----+---+---+-------+---+----+---+---- + | 0 | | zero | 0 | | 0 | + | 0 | | zero | 0 | | 1 | -1 + | 0 | | zero | 0 | | 2 | 2 + | 0 | | zero | 0 | | 2 | 4 + | 0 | | zero | 0 | | 3 | -3 + | 0 | | zero | 0 | | 5 | -5 + | 0 | | zero | 0 | | 5 | -5 + | 0 | | zero | 0 | | | 0 + | 0 | | zero | 0 | | | + | 0 | | zero | 1 | -1 | 0 | + | 0 | | zero | 1 | -1 | 1 | -1 + | 0 | | zero | 1 | -1 | 2 | 2 + | 0 | | zero | 1 | -1 | 2 | 4 + | 0 | | zero | 1 | -1 | 3 | -3 + | 0 | | zero | 1 | -1 | 5 | -5 + | 0 | | zero | 1 | -1 | 5 | -5 + | 0 | | zero | 1 | -1 | | 0 + | 0 | | zero | 1 | -1 | | + | 0 | | zero | 2 | 2 | 0 | + | 0 | | zero | 2 | 2 | 1 | -1 + | 0 | | zero | 2 | 2 | 2 | 2 + | 0 | | zero | 2 | 2 | 2 | 4 + | 0 | | zero | 2 | 2 | 3 | -3 + | 0 | | zero | 2 | 2 | 5 | -5 + | 0 | | zero | 2 | 2 | 5 | -5 + | 0 | | zero | 2 | 2 | | 0 + | 0 | | zero | 2 | 2 | | + | 0 | | zero | 2 | 4 | 0 | + | 0 | | zero | 2 | 4 | 1 | -1 + | 0 | | zero | 2 | 4 | 2 | 2 + | 0 | | zero | 2 | 4 | 2 | 4 + | 0 | | zero | 2 | 4 | 3 | -3 + | 0 | | zero | 2 | 4 | 5 | -5 + | 0 | | zero | 2 | 4 | 5 | -5 + | 0 | | zero | 2 | 4 | | 0 + | 0 | | zero | 2 | 4 | | + | 0 | | zero | 3 | -3 | 0 | + | 0 | | zero | 3 | -3 | 1 | -1 + | 0 | | zero | 3 | -3 | 2 | 2 + | 0 | | zero | 3 | -3 | 2 | 4 + | 0 | | zero | 3 | -3 | 3 | -3 + | 0 | | zero | 3 | -3 | 5 | -5 + | 0 | | zero | 3 | -3 | 5 | -5 + | 0 | | zero | 3 | -3 | | 0 + | 0 | | zero | 3 | -3 | | + | 0 | | zero | 5 | -5 | 0 | + | 0 | | zero | 5 | -5 | 0 | + | 0 | | zero | 5 | -5 | 1 | -1 + | 0 | | zero | 5 | -5 | 1 | -1 + | 0 | | zero | 5 | -5 | 2 | 2 + | 0 | | zero | 5 | -5 | 2 | 2 + | 0 | | zero | 5 | -5 | 2 | 4 + | 0 | | zero | 5 | -5 | 2 | 4 + | 0 | | zero | 5 | -5 | 3 | -3 + | 0 | | zero | 5 | -5 | 3 | -3 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | | 0 + | 0 | | zero | 5 | -5 | | 0 + | 0 | | zero | 5 | -5 | | + | 0 | | zero | 5 | -5 | | + | 0 | | zero | | 0 | 0 | + | 0 | | zero | | 0 | 1 | -1 + | 0 | | zero | | 0 | 2 | 2 + | 0 | | zero | | 0 | 2 | 4 + | 0 | | zero | | 0 | 3 | -3 + | 0 | | zero | | 0 | 5 | -5 + | 0 | | zero | | 0 | 5 | -5 + | 0 | | zero | | 0 | | 0 + | 0 | | zero | | 0 | | + | 0 | | zero | | | 0 | + | 0 | | zero | | | 1 | -1 + | 0 | | zero | | | 2 | 2 + | 0 | | zero | | | 2 | 4 + | 0 | | zero | | | 3 | -3 + | 0 | | zero | | | 5 | -5 + | 0 | | zero | | | 5 | -5 + | 0 | | zero | | | | 0 + | 0 | | zero | | | | + | 1 | 4 | one | 0 | | 0 | + | 1 | 4 | one | 0 | | 1 | -1 + | 1 | 4 | one | 0 | | 2 | 2 + | 1 | 4 | one | 0 | | 2 | 4 + | 1 | 4 | one | 0 | | 3 | -3 + | 1 | 4 | one | 0 | | 5 | -5 + | 1 | 4 | one | 0 | | 5 | -5 + | 1 | 4 | one | 0 | | | 0 + | 1 | 4 | one | 0 | | | + | 1 | 4 | one | 1 | -1 | 0 | + | 1 | 4 | one | 1 | -1 | 1 | -1 + | 1 | 4 | one | 1 | -1 | 2 | 2 + | 1 | 4 | one | 1 | -1 | 2 | 4 + | 1 | 4 | one | 1 | -1 | 3 | -3 + | 1 | 4 | one | 1 | -1 | 5 | -5 + | 1 | 4 | one | 1 | -1 | 5 | -5 + | 1 | 4 | one | 1 | -1 | | 0 + | 1 | 4 | one | 1 | -1 | | + | 1 | 4 | one | 2 | 2 | 0 | + | 1 | 4 | one | 2 | 2 | 1 | -1 + | 1 | 4 | one | 2 | 2 | 2 | 2 + | 1 | 4 | one | 2 | 2 | 2 | 4 + | 1 | 4 | one | 2 | 2 | 3 | -3 + | 1 | 4 | one | 2 | 2 | 5 | -5 + | 1 | 4 | one | 2 | 2 | 5 | -5 + | 1 | 4 | one | 2 | 2 | | 0 + | 1 | 4 | one | 2 | 2 | | + | 1 | 4 | one | 2 | 4 | 0 | + | 1 | 4 | one | 2 | 4 | 1 | -1 + | 1 | 4 | one | 2 | 4 | 2 | 2 + | 1 | 4 | one | 2 | 4 | 2 | 4 + | 1 | 4 | one | 2 | 4 | 3 | -3 + | 1 | 4 | one | 2 | 4 | 5 | -5 + | 1 | 4 | one | 2 | 4 | 5 | -5 + | 1 | 4 | one | 2 | 4 | | 0 + | 1 | 4 | one | 2 | 4 | | + | 1 | 4 | one | 3 | -3 | 0 | + | 1 | 4 | one | 3 | -3 | 1 | -1 + | 1 | 4 | one | 3 | -3 | 2 | 2 + | 1 | 4 | one | 3 | -3 | 2 | 4 + | 1 | 4 | one | 3 | -3 | 3 | -3 + | 1 | 4 | one | 3 | -3 | 5 | -5 + | 1 | 4 | one | 3 | -3 | 5 | -5 + | 1 | 4 | one | 3 | -3 | | 0 + | 1 | 4 | one | 3 | -3 | | + | 1 | 4 | one | 5 | -5 | 0 | + | 1 | 4 | one | 5 | -5 | 0 | + | 1 | 4 | one | 5 | -5 | 1 | -1 + | 1 | 4 | one | 5 | -5 | 1 | -1 + | 1 | 4 | one | 5 | -5 | 2 | 2 + | 1 | 4 | one | 5 | -5 | 2 | 2 + | 1 | 4 | one | 5 | -5 | 2 | 4 + | 1 | 4 | one | 5 | -5 | 2 | 4 + | 1 | 4 | one | 5 | -5 | 3 | -3 + | 1 | 4 | one | 5 | -5 | 3 | -3 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | | 0 + | 1 | 4 | one | 5 | -5 | | 0 + | 1 | 4 | one | 5 | -5 | | + | 1 | 4 | one | 5 | -5 | | + | 1 | 4 | one | | 0 | 0 | + | 1 | 4 | one | | 0 | 1 | -1 + | 1 | 4 | one | | 0 | 2 | 2 + | 1 | 4 | one | | 0 | 2 | 4 + | 1 | 4 | one | | 0 | 3 | -3 + | 1 | 4 | one | | 0 | 5 | -5 + | 1 | 4 | one | | 0 | 5 | -5 + | 1 | 4 | one | | 0 | | 0 + | 1 | 4 | one | | 0 | | + | 1 | 4 | one | | | 0 | + | 1 | 4 | one | | | 1 | -1 + | 1 | 4 | one | | | 2 | 2 + | 1 | 4 | one | | | 2 | 4 + | 1 | 4 | one | | | 3 | -3 + | 1 | 4 | one | | | 5 | -5 + | 1 | 4 | one | | | 5 | -5 + | 1 | 4 | one | | | | 0 + | 1 | 4 | one | | | | + | 2 | 3 | two | 0 | | 0 | + | 2 | 3 | two | 0 | | 1 | -1 + | 2 | 3 | two | 0 | | 2 | 2 + | 2 | 3 | two | 0 | | 2 | 4 + | 2 | 3 | two | 0 | | 3 | -3 + | 2 | 3 | two | 0 | | 5 | -5 + | 2 | 3 | two | 0 | | 5 | -5 + | 2 | 3 | two | 0 | | | 0 + | 2 | 3 | two | 0 | | | + | 2 | 3 | two | 1 | -1 | 0 | + | 2 | 3 | two | 1 | -1 | 1 | -1 + | 2 | 3 | two | 1 | -1 | 2 | 2 + | 2 | 3 | two | 1 | -1 | 2 | 4 + | 2 | 3 | two | 1 | -1 | 3 | -3 + | 2 | 3 | two | 1 | -1 | 5 | -5 + | 2 | 3 | two | 1 | -1 | 5 | -5 + | 2 | 3 | two | 1 | -1 | | 0 + | 2 | 3 | two | 1 | -1 | | + | 2 | 3 | two | 2 | 2 | 0 | + | 2 | 3 | two | 2 | 2 | 1 | -1 + | 2 | 3 | two | 2 | 2 | 2 | 2 + | 2 | 3 | two | 2 | 2 | 2 | 4 + | 2 | 3 | two | 2 | 2 | 3 | -3 + | 2 | 3 | two | 2 | 2 | 5 | -5 + | 2 | 3 | two | 2 | 2 | 5 | -5 + | 2 | 3 | two | 2 | 2 | | 0 + | 2 | 3 | two | 2 | 2 | | + | 2 | 3 | two | 2 | 4 | 0 | + | 2 | 3 | two | 2 | 4 | 1 | -1 + | 2 | 3 | two | 2 | 4 | 2 | 2 + | 2 | 3 | two | 2 | 4 | 2 | 4 + | 2 | 3 | two | 2 | 4 | 3 | -3 + | 2 | 3 | two | 2 | 4 | 5 | -5 + | 2 | 3 | two | 2 | 4 | 5 | -5 + | 2 | 3 | two | 2 | 4 | | 0 + | 2 | 3 | two | 2 | 4 | | + | 2 | 3 | two | 3 | -3 | 0 | + | 2 | 3 | two | 3 | -3 | 1 | -1 + | 2 | 3 | two | 3 | -3 | 2 | 2 + | 2 | 3 | two | 3 | -3 | 2 | 4 + | 2 | 3 | two | 3 | -3 | 3 | -3 + | 2 | 3 | two | 3 | -3 | 5 | -5 + | 2 | 3 | two | 3 | -3 | 5 | -5 + | 2 | 3 | two | 3 | -3 | | 0 + | 2 | 3 | two | 3 | -3 | | + | 2 | 3 | two | 5 | -5 | 0 | + | 2 | 3 | two | 5 | -5 | 0 | + | 2 | 3 | two | 5 | -5 | 1 | -1 + | 2 | 3 | two | 5 | -5 | 1 | -1 + | 2 | 3 | two | 5 | -5 | 2 | 2 + | 2 | 3 | two | 5 | -5 | 2 | 2 + | 2 | 3 | two | 5 | -5 | 2 | 4 + | 2 | 3 | two | 5 | -5 | 2 | 4 + | 2 | 3 | two | 5 | -5 | 3 | -3 + | 2 | 3 | two | 5 | -5 | 3 | -3 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | | 0 + | 2 | 3 | two | 5 | -5 | | 0 + | 2 | 3 | two | 5 | -5 | | + | 2 | 3 | two | 5 | -5 | | + | 2 | 3 | two | | 0 | 0 | + | 2 | 3 | two | | 0 | 1 | -1 + | 2 | 3 | two | | 0 | 2 | 2 + | 2 | 3 | two | | 0 | 2 | 4 + | 2 | 3 | two | | 0 | 3 | -3 + | 2 | 3 | two | | 0 | 5 | -5 + | 2 | 3 | two | | 0 | 5 | -5 + | 2 | 3 | two | | 0 | | 0 + | 2 | 3 | two | | 0 | | + | 2 | 3 | two | | | 0 | + | 2 | 3 | two | | | 1 | -1 + | 2 | 3 | two | | | 2 | 2 + | 2 | 3 | two | | | 2 | 4 + | 2 | 3 | two | | | 3 | -3 + | 2 | 3 | two | | | 5 | -5 + | 2 | 3 | two | | | 5 | -5 + | 2 | 3 | two | | | | 0 + | 2 | 3 | two | | | | + | 3 | 2 | three | 0 | | 0 | + | 3 | 2 | three | 0 | | 1 | -1 + | 3 | 2 | three | 0 | | 2 | 2 + | 3 | 2 | three | 0 | | 2 | 4 + | 3 | 2 | three | 0 | | 3 | -3 + | 3 | 2 | three | 0 | | 5 | -5 + | 3 | 2 | three | 0 | | 5 | -5 + | 3 | 2 | three | 0 | | | 0 + | 3 | 2 | three | 0 | | | + | 3 | 2 | three | 1 | -1 | 0 | + | 3 | 2 | three | 1 | -1 | 1 | -1 + | 3 | 2 | three | 1 | -1 | 2 | 2 + | 3 | 2 | three | 1 | -1 | 2 | 4 + | 3 | 2 | three | 1 | -1 | 3 | -3 + | 3 | 2 | three | 1 | -1 | 5 | -5 + | 3 | 2 | three | 1 | -1 | 5 | -5 + | 3 | 2 | three | 1 | -1 | | 0 + | 3 | 2 | three | 1 | -1 | | + | 3 | 2 | three | 2 | 2 | 0 | + | 3 | 2 | three | 2 | 2 | 1 | -1 + | 3 | 2 | three | 2 | 2 | 2 | 2 + | 3 | 2 | three | 2 | 2 | 2 | 4 + | 3 | 2 | three | 2 | 2 | 3 | -3 + | 3 | 2 | three | 2 | 2 | 5 | -5 + | 3 | 2 | three | 2 | 2 | 5 | -5 + | 3 | 2 | three | 2 | 2 | | 0 + | 3 | 2 | three | 2 | 2 | | + | 3 | 2 | three | 2 | 4 | 0 | + | 3 | 2 | three | 2 | 4 | 1 | -1 + | 3 | 2 | three | 2 | 4 | 2 | 2 + | 3 | 2 | three | 2 | 4 | 2 | 4 + | 3 | 2 | three | 2 | 4 | 3 | -3 + | 3 | 2 | three | 2 | 4 | 5 | -5 + | 3 | 2 | three | 2 | 4 | 5 | -5 + | 3 | 2 | three | 2 | 4 | | 0 + | 3 | 2 | three | 2 | 4 | | + | 3 | 2 | three | 3 | -3 | 0 | + | 3 | 2 | three | 3 | -3 | 1 | -1 + | 3 | 2 | three | 3 | -3 | 2 | 2 + | 3 | 2 | three | 3 | -3 | 2 | 4 + | 3 | 2 | three | 3 | -3 | 3 | -3 + | 3 | 2 | three | 3 | -3 | 5 | -5 + | 3 | 2 | three | 3 | -3 | 5 | -5 + | 3 | 2 | three | 3 | -3 | | 0 + | 3 | 2 | three | 3 | -3 | | + | 3 | 2 | three | 5 | -5 | 0 | + | 3 | 2 | three | 5 | -5 | 0 | + | 3 | 2 | three | 5 | -5 | 1 | -1 + | 3 | 2 | three | 5 | -5 | 1 | -1 + | 3 | 2 | three | 5 | -5 | 2 | 2 + | 3 | 2 | three | 5 | -5 | 2 | 2 + | 3 | 2 | three | 5 | -5 | 2 | 4 + | 3 | 2 | three | 5 | -5 | 2 | 4 + | 3 | 2 | three | 5 | -5 | 3 | -3 + | 3 | 2 | three | 5 | -5 | 3 | -3 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | | 0 + | 3 | 2 | three | 5 | -5 | | 0 + | 3 | 2 | three | 5 | -5 | | + | 3 | 2 | three | 5 | -5 | | + | 3 | 2 | three | | 0 | 0 | + | 3 | 2 | three | | 0 | 1 | -1 + | 3 | 2 | three | | 0 | 2 | 2 + | 3 | 2 | three | | 0 | 2 | 4 + | 3 | 2 | three | | 0 | 3 | -3 + | 3 | 2 | three | | 0 | 5 | -5 + | 3 | 2 | three | | 0 | 5 | -5 + | 3 | 2 | three | | 0 | | 0 + | 3 | 2 | three | | 0 | | + | 3 | 2 | three | | | 0 | + | 3 | 2 | three | | | 1 | -1 + | 3 | 2 | three | | | 2 | 2 + | 3 | 2 | three | | | 2 | 4 + | 3 | 2 | three | | | 3 | -3 + | 3 | 2 | three | | | 5 | -5 + | 3 | 2 | three | | | 5 | -5 + | 3 | 2 | three | | | | 0 + | 3 | 2 | three | | | | + | 4 | 1 | four | 0 | | 0 | + | 4 | 1 | four | 0 | | 1 | -1 + | 4 | 1 | four | 0 | | 2 | 2 + | 4 | 1 | four | 0 | | 2 | 4 + | 4 | 1 | four | 0 | | 3 | -3 + | 4 | 1 | four | 0 | | 5 | -5 + | 4 | 1 | four | 0 | | 5 | -5 + | 4 | 1 | four | 0 | | | 0 + | 4 | 1 | four | 0 | | | + | 4 | 1 | four | 1 | -1 | 0 | + | 4 | 1 | four | 1 | -1 | 1 | -1 + | 4 | 1 | four | 1 | -1 | 2 | 2 + | 4 | 1 | four | 1 | -1 | 2 | 4 + | 4 | 1 | four | 1 | -1 | 3 | -3 + | 4 | 1 | four | 1 | -1 | 5 | -5 + | 4 | 1 | four | 1 | -1 | 5 | -5 + | 4 | 1 | four | 1 | -1 | | 0 + | 4 | 1 | four | 1 | -1 | | + | 4 | 1 | four | 2 | 2 | 0 | + | 4 | 1 | four | 2 | 2 | 1 | -1 + | 4 | 1 | four | 2 | 2 | 2 | 2 + | 4 | 1 | four | 2 | 2 | 2 | 4 + | 4 | 1 | four | 2 | 2 | 3 | -3 + | 4 | 1 | four | 2 | 2 | 5 | -5 + | 4 | 1 | four | 2 | 2 | 5 | -5 + | 4 | 1 | four | 2 | 2 | | 0 + | 4 | 1 | four | 2 | 2 | | + | 4 | 1 | four | 2 | 4 | 0 | + | 4 | 1 | four | 2 | 4 | 1 | -1 + | 4 | 1 | four | 2 | 4 | 2 | 2 + | 4 | 1 | four | 2 | 4 | 2 | 4 + | 4 | 1 | four | 2 | 4 | 3 | -3 + | 4 | 1 | four | 2 | 4 | 5 | -5 + | 4 | 1 | four | 2 | 4 | 5 | -5 + | 4 | 1 | four | 2 | 4 | | 0 + | 4 | 1 | four | 2 | 4 | | + | 4 | 1 | four | 3 | -3 | 0 | + | 4 | 1 | four | 3 | -3 | 1 | -1 + | 4 | 1 | four | 3 | -3 | 2 | 2 + | 4 | 1 | four | 3 | -3 | 2 | 4 + | 4 | 1 | four | 3 | -3 | 3 | -3 + | 4 | 1 | four | 3 | -3 | 5 | -5 + | 4 | 1 | four | 3 | -3 | 5 | -5 + | 4 | 1 | four | 3 | -3 | | 0 + | 4 | 1 | four | 3 | -3 | | + | 4 | 1 | four | 5 | -5 | 0 | + | 4 | 1 | four | 5 | -5 | 0 | + | 4 | 1 | four | 5 | -5 | 1 | -1 + | 4 | 1 | four | 5 | -5 | 1 | -1 + | 4 | 1 | four | 5 | -5 | 2 | 2 + | 4 | 1 | four | 5 | -5 | 2 | 2 + | 4 | 1 | four | 5 | -5 | 2 | 4 + | 4 | 1 | four | 5 | -5 | 2 | 4 + | 4 | 1 | four | 5 | -5 | 3 | -3 + | 4 | 1 | four | 5 | -5 | 3 | -3 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | | 0 + | 4 | 1 | four | 5 | -5 | | 0 + | 4 | 1 | four | 5 | -5 | | + | 4 | 1 | four | 5 | -5 | | + | 4 | 1 | four | | 0 | 0 | + | 4 | 1 | four | | 0 | 1 | -1 + | 4 | 1 | four | | 0 | 2 | 2 + | 4 | 1 | four | | 0 | 2 | 4 + | 4 | 1 | four | | 0 | 3 | -3 + | 4 | 1 | four | | 0 | 5 | -5 + | 4 | 1 | four | | 0 | 5 | -5 + | 4 | 1 | four | | 0 | | 0 + | 4 | 1 | four | | 0 | | + | 4 | 1 | four | | | 0 | + | 4 | 1 | four | | | 1 | -1 + | 4 | 1 | four | | | 2 | 2 + | 4 | 1 | four | | | 2 | 4 + | 4 | 1 | four | | | 3 | -3 + | 4 | 1 | four | | | 5 | -5 + | 4 | 1 | four | | | 5 | -5 + | 4 | 1 | four | | | | 0 + | 4 | 1 | four | | | | + | 5 | 0 | five | 0 | | 0 | + | 5 | 0 | five | 0 | | 1 | -1 + | 5 | 0 | five | 0 | | 2 | 2 + | 5 | 0 | five | 0 | | 2 | 4 + | 5 | 0 | five | 0 | | 3 | -3 + | 5 | 0 | five | 0 | | 5 | -5 + | 5 | 0 | five | 0 | | 5 | -5 + | 5 | 0 | five | 0 | | | 0 + | 5 | 0 | five | 0 | | | + | 5 | 0 | five | 1 | -1 | 0 | + | 5 | 0 | five | 1 | -1 | 1 | -1 + | 5 | 0 | five | 1 | -1 | 2 | 2 + | 5 | 0 | five | 1 | -1 | 2 | 4 + | 5 | 0 | five | 1 | -1 | 3 | -3 + | 5 | 0 | five | 1 | -1 | 5 | -5 + | 5 | 0 | five | 1 | -1 | 5 | -5 + | 5 | 0 | five | 1 | -1 | | 0 + | 5 | 0 | five | 1 | -1 | | + | 5 | 0 | five | 2 | 2 | 0 | + | 5 | 0 | five | 2 | 2 | 1 | -1 + | 5 | 0 | five | 2 | 2 | 2 | 2 + | 5 | 0 | five | 2 | 2 | 2 | 4 + | 5 | 0 | five | 2 | 2 | 3 | -3 + | 5 | 0 | five | 2 | 2 | 5 | -5 + | 5 | 0 | five | 2 | 2 | 5 | -5 + | 5 | 0 | five | 2 | 2 | | 0 + | 5 | 0 | five | 2 | 2 | | + | 5 | 0 | five | 2 | 4 | 0 | + | 5 | 0 | five | 2 | 4 | 1 | -1 + | 5 | 0 | five | 2 | 4 | 2 | 2 + | 5 | 0 | five | 2 | 4 | 2 | 4 + | 5 | 0 | five | 2 | 4 | 3 | -3 + | 5 | 0 | five | 2 | 4 | 5 | -5 + | 5 | 0 | five | 2 | 4 | 5 | -5 + | 5 | 0 | five | 2 | 4 | | 0 + | 5 | 0 | five | 2 | 4 | | + | 5 | 0 | five | 3 | -3 | 0 | + | 5 | 0 | five | 3 | -3 | 1 | -1 + | 5 | 0 | five | 3 | -3 | 2 | 2 + | 5 | 0 | five | 3 | -3 | 2 | 4 + | 5 | 0 | five | 3 | -3 | 3 | -3 + | 5 | 0 | five | 3 | -3 | 5 | -5 + | 5 | 0 | five | 3 | -3 | 5 | -5 + | 5 | 0 | five | 3 | -3 | | 0 + | 5 | 0 | five | 3 | -3 | | + | 5 | 0 | five | 5 | -5 | 0 | + | 5 | 0 | five | 5 | -5 | 0 | + | 5 | 0 | five | 5 | -5 | 1 | -1 + | 5 | 0 | five | 5 | -5 | 1 | -1 + | 5 | 0 | five | 5 | -5 | 2 | 2 + | 5 | 0 | five | 5 | -5 | 2 | 2 + | 5 | 0 | five | 5 | -5 | 2 | 4 + | 5 | 0 | five | 5 | -5 | 2 | 4 + | 5 | 0 | five | 5 | -5 | 3 | -3 + | 5 | 0 | five | 5 | -5 | 3 | -3 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | | 0 + | 5 | 0 | five | 5 | -5 | | 0 + | 5 | 0 | five | 5 | -5 | | + | 5 | 0 | five | 5 | -5 | | + | 5 | 0 | five | | 0 | 0 | + | 5 | 0 | five | | 0 | 1 | -1 + | 5 | 0 | five | | 0 | 2 | 2 + | 5 | 0 | five | | 0 | 2 | 4 + | 5 | 0 | five | | 0 | 3 | -3 + | 5 | 0 | five | | 0 | 5 | -5 + | 5 | 0 | five | | 0 | 5 | -5 + | 5 | 0 | five | | 0 | | 0 + | 5 | 0 | five | | 0 | | + | 5 | 0 | five | | | 0 | + | 5 | 0 | five | | | 1 | -1 + | 5 | 0 | five | | | 2 | 2 + | 5 | 0 | five | | | 2 | 4 + | 5 | 0 | five | | | 3 | -3 + | 5 | 0 | five | | | 5 | -5 + | 5 | 0 | five | | | 5 | -5 + | 5 | 0 | five | | | | 0 + | 5 | 0 | five | | | | + | 6 | 6 | six | 0 | | 0 | + | 6 | 6 | six | 0 | | 1 | -1 + | 6 | 6 | six | 0 | | 2 | 2 + | 6 | 6 | six | 0 | | 2 | 4 + | 6 | 6 | six | 0 | | 3 | -3 + | 6 | 6 | six | 0 | | 5 | -5 + | 6 | 6 | six | 0 | | 5 | -5 + | 6 | 6 | six | 0 | | | 0 + | 6 | 6 | six | 0 | | | + | 6 | 6 | six | 1 | -1 | 0 | + | 6 | 6 | six | 1 | -1 | 1 | -1 + | 6 | 6 | six | 1 | -1 | 2 | 2 + | 6 | 6 | six | 1 | -1 | 2 | 4 + | 6 | 6 | six | 1 | -1 | 3 | -3 + | 6 | 6 | six | 1 | -1 | 5 | -5 + | 6 | 6 | six | 1 | -1 | 5 | -5 + | 6 | 6 | six | 1 | -1 | | 0 + | 6 | 6 | six | 1 | -1 | | + | 6 | 6 | six | 2 | 2 | 0 | + | 6 | 6 | six | 2 | 2 | 1 | -1 + | 6 | 6 | six | 2 | 2 | 2 | 2 + | 6 | 6 | six | 2 | 2 | 2 | 4 + | 6 | 6 | six | 2 | 2 | 3 | -3 + | 6 | 6 | six | 2 | 2 | 5 | -5 + | 6 | 6 | six | 2 | 2 | 5 | -5 + | 6 | 6 | six | 2 | 2 | | 0 + | 6 | 6 | six | 2 | 2 | | + | 6 | 6 | six | 2 | 4 | 0 | + | 6 | 6 | six | 2 | 4 | 1 | -1 + | 6 | 6 | six | 2 | 4 | 2 | 2 + | 6 | 6 | six | 2 | 4 | 2 | 4 + | 6 | 6 | six | 2 | 4 | 3 | -3 + | 6 | 6 | six | 2 | 4 | 5 | -5 + | 6 | 6 | six | 2 | 4 | 5 | -5 + | 6 | 6 | six | 2 | 4 | | 0 + | 6 | 6 | six | 2 | 4 | | + | 6 | 6 | six | 3 | -3 | 0 | + | 6 | 6 | six | 3 | -3 | 1 | -1 + | 6 | 6 | six | 3 | -3 | 2 | 2 + | 6 | 6 | six | 3 | -3 | 2 | 4 + | 6 | 6 | six | 3 | -3 | 3 | -3 + | 6 | 6 | six | 3 | -3 | 5 | -5 + | 6 | 6 | six | 3 | -3 | 5 | -5 + | 6 | 6 | six | 3 | -3 | | 0 + | 6 | 6 | six | 3 | -3 | | + | 6 | 6 | six | 5 | -5 | 0 | + | 6 | 6 | six | 5 | -5 | 0 | + | 6 | 6 | six | 5 | -5 | 1 | -1 + | 6 | 6 | six | 5 | -5 | 1 | -1 + | 6 | 6 | six | 5 | -5 | 2 | 2 + | 6 | 6 | six | 5 | -5 | 2 | 2 + | 6 | 6 | six | 5 | -5 | 2 | 4 + | 6 | 6 | six | 5 | -5 | 2 | 4 + | 6 | 6 | six | 5 | -5 | 3 | -3 + | 6 | 6 | six | 5 | -5 | 3 | -3 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | | 0 + | 6 | 6 | six | 5 | -5 | | 0 + | 6 | 6 | six | 5 | -5 | | + | 6 | 6 | six | 5 | -5 | | + | 6 | 6 | six | | 0 | 0 | + | 6 | 6 | six | | 0 | 1 | -1 + | 6 | 6 | six | | 0 | 2 | 2 + | 6 | 6 | six | | 0 | 2 | 4 + | 6 | 6 | six | | 0 | 3 | -3 + | 6 | 6 | six | | 0 | 5 | -5 + | 6 | 6 | six | | 0 | 5 | -5 + | 6 | 6 | six | | 0 | | 0 + | 6 | 6 | six | | 0 | | + | 6 | 6 | six | | | 0 | + | 6 | 6 | six | | | 1 | -1 + | 6 | 6 | six | | | 2 | 2 + | 6 | 6 | six | | | 2 | 4 + | 6 | 6 | six | | | 3 | -3 + | 6 | 6 | six | | | 5 | -5 + | 6 | 6 | six | | | 5 | -5 + | 6 | 6 | six | | | | 0 + | 6 | 6 | six | | | | + | 7 | 7 | seven | 0 | | 0 | + | 7 | 7 | seven | 0 | | 1 | -1 + | 7 | 7 | seven | 0 | | 2 | 2 + | 7 | 7 | seven | 0 | | 2 | 4 + | 7 | 7 | seven | 0 | | 3 | -3 + | 7 | 7 | seven | 0 | | 5 | -5 + | 7 | 7 | seven | 0 | | 5 | -5 + | 7 | 7 | seven | 0 | | | 0 + | 7 | 7 | seven | 0 | | | + | 7 | 7 | seven | 1 | -1 | 0 | + | 7 | 7 | seven | 1 | -1 | 1 | -1 + | 7 | 7 | seven | 1 | -1 | 2 | 2 + | 7 | 7 | seven | 1 | -1 | 2 | 4 + | 7 | 7 | seven | 1 | -1 | 3 | -3 + | 7 | 7 | seven | 1 | -1 | 5 | -5 + | 7 | 7 | seven | 1 | -1 | 5 | -5 + | 7 | 7 | seven | 1 | -1 | | 0 + | 7 | 7 | seven | 1 | -1 | | + | 7 | 7 | seven | 2 | 2 | 0 | + | 7 | 7 | seven | 2 | 2 | 1 | -1 + | 7 | 7 | seven | 2 | 2 | 2 | 2 + | 7 | 7 | seven | 2 | 2 | 2 | 4 + | 7 | 7 | seven | 2 | 2 | 3 | -3 + | 7 | 7 | seven | 2 | 2 | 5 | -5 + | 7 | 7 | seven | 2 | 2 | 5 | -5 + | 7 | 7 | seven | 2 | 2 | | 0 + | 7 | 7 | seven | 2 | 2 | | + | 7 | 7 | seven | 2 | 4 | 0 | + | 7 | 7 | seven | 2 | 4 | 1 | -1 + | 7 | 7 | seven | 2 | 4 | 2 | 2 + | 7 | 7 | seven | 2 | 4 | 2 | 4 + | 7 | 7 | seven | 2 | 4 | 3 | -3 + | 7 | 7 | seven | 2 | 4 | 5 | -5 + | 7 | 7 | seven | 2 | 4 | 5 | -5 + | 7 | 7 | seven | 2 | 4 | | 0 + | 7 | 7 | seven | 2 | 4 | | + | 7 | 7 | seven | 3 | -3 | 0 | + | 7 | 7 | seven | 3 | -3 | 1 | -1 + | 7 | 7 | seven | 3 | -3 | 2 | 2 + | 7 | 7 | seven | 3 | -3 | 2 | 4 + | 7 | 7 | seven | 3 | -3 | 3 | -3 + | 7 | 7 | seven | 3 | -3 | 5 | -5 + | 7 | 7 | seven | 3 | -3 | 5 | -5 + | 7 | 7 | seven | 3 | -3 | | 0 + | 7 | 7 | seven | 3 | -3 | | + | 7 | 7 | seven | 5 | -5 | 0 | + | 7 | 7 | seven | 5 | -5 | 0 | + | 7 | 7 | seven | 5 | -5 | 1 | -1 + | 7 | 7 | seven | 5 | -5 | 1 | -1 + | 7 | 7 | seven | 5 | -5 | 2 | 2 + | 7 | 7 | seven | 5 | -5 | 2 | 2 + | 7 | 7 | seven | 5 | -5 | 2 | 4 + | 7 | 7 | seven | 5 | -5 | 2 | 4 + | 7 | 7 | seven | 5 | -5 | 3 | -3 + | 7 | 7 | seven | 5 | -5 | 3 | -3 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | | 0 + | 7 | 7 | seven | 5 | -5 | | 0 + | 7 | 7 | seven | 5 | -5 | | + | 7 | 7 | seven | 5 | -5 | | + | 7 | 7 | seven | | 0 | 0 | + | 7 | 7 | seven | | 0 | 1 | -1 + | 7 | 7 | seven | | 0 | 2 | 2 + | 7 | 7 | seven | | 0 | 2 | 4 + | 7 | 7 | seven | | 0 | 3 | -3 + | 7 | 7 | seven | | 0 | 5 | -5 + | 7 | 7 | seven | | 0 | 5 | -5 + | 7 | 7 | seven | | 0 | | 0 + | 7 | 7 | seven | | 0 | | + | 7 | 7 | seven | | | 0 | + | 7 | 7 | seven | | | 1 | -1 + | 7 | 7 | seven | | | 2 | 2 + | 7 | 7 | seven | | | 2 | 4 + | 7 | 7 | seven | | | 3 | -3 + | 7 | 7 | seven | | | 5 | -5 + | 7 | 7 | seven | | | 5 | -5 + | 7 | 7 | seven | | | | 0 + | 7 | 7 | seven | | | | + | 8 | 8 | eight | 0 | | 0 | + | 8 | 8 | eight | 0 | | 1 | -1 + | 8 | 8 | eight | 0 | | 2 | 2 + | 8 | 8 | eight | 0 | | 2 | 4 + | 8 | 8 | eight | 0 | | 3 | -3 + | 8 | 8 | eight | 0 | | 5 | -5 + | 8 | 8 | eight | 0 | | 5 | -5 + | 8 | 8 | eight | 0 | | | 0 + | 8 | 8 | eight | 0 | | | + | 8 | 8 | eight | 1 | -1 | 0 | + | 8 | 8 | eight | 1 | -1 | 1 | -1 + | 8 | 8 | eight | 1 | -1 | 2 | 2 + | 8 | 8 | eight | 1 | -1 | 2 | 4 + | 8 | 8 | eight | 1 | -1 | 3 | -3 + | 8 | 8 | eight | 1 | -1 | 5 | -5 + | 8 | 8 | eight | 1 | -1 | 5 | -5 + | 8 | 8 | eight | 1 | -1 | | 0 + | 8 | 8 | eight | 1 | -1 | | + | 8 | 8 | eight | 2 | 2 | 0 | + | 8 | 8 | eight | 2 | 2 | 1 | -1 + | 8 | 8 | eight | 2 | 2 | 2 | 2 + | 8 | 8 | eight | 2 | 2 | 2 | 4 + | 8 | 8 | eight | 2 | 2 | 3 | -3 + | 8 | 8 | eight | 2 | 2 | 5 | -5 + | 8 | 8 | eight | 2 | 2 | 5 | -5 + | 8 | 8 | eight | 2 | 2 | | 0 + | 8 | 8 | eight | 2 | 2 | | + | 8 | 8 | eight | 2 | 4 | 0 | + | 8 | 8 | eight | 2 | 4 | 1 | -1 + | 8 | 8 | eight | 2 | 4 | 2 | 2 + | 8 | 8 | eight | 2 | 4 | 2 | 4 + | 8 | 8 | eight | 2 | 4 | 3 | -3 + | 8 | 8 | eight | 2 | 4 | 5 | -5 + | 8 | 8 | eight | 2 | 4 | 5 | -5 + | 8 | 8 | eight | 2 | 4 | | 0 + | 8 | 8 | eight | 2 | 4 | | + | 8 | 8 | eight | 3 | -3 | 0 | + | 8 | 8 | eight | 3 | -3 | 1 | -1 + | 8 | 8 | eight | 3 | -3 | 2 | 2 + | 8 | 8 | eight | 3 | -3 | 2 | 4 + | 8 | 8 | eight | 3 | -3 | 3 | -3 + | 8 | 8 | eight | 3 | -3 | 5 | -5 + | 8 | 8 | eight | 3 | -3 | 5 | -5 + | 8 | 8 | eight | 3 | -3 | | 0 + | 8 | 8 | eight | 3 | -3 | | + | 8 | 8 | eight | 5 | -5 | 0 | + | 8 | 8 | eight | 5 | -5 | 0 | + | 8 | 8 | eight | 5 | -5 | 1 | -1 + | 8 | 8 | eight | 5 | -5 | 1 | -1 + | 8 | 8 | eight | 5 | -5 | 2 | 2 + | 8 | 8 | eight | 5 | -5 | 2 | 2 + | 8 | 8 | eight | 5 | -5 | 2 | 4 + | 8 | 8 | eight | 5 | -5 | 2 | 4 + | 8 | 8 | eight | 5 | -5 | 3 | -3 + | 8 | 8 | eight | 5 | -5 | 3 | -3 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | | 0 + | 8 | 8 | eight | 5 | -5 | | 0 + | 8 | 8 | eight | 5 | -5 | | + | 8 | 8 | eight | 5 | -5 | | + | 8 | 8 | eight | | 0 | 0 | + | 8 | 8 | eight | | 0 | 1 | -1 + | 8 | 8 | eight | | 0 | 2 | 2 + | 8 | 8 | eight | | 0 | 2 | 4 + | 8 | 8 | eight | | 0 | 3 | -3 + | 8 | 8 | eight | | 0 | 5 | -5 + | 8 | 8 | eight | | 0 | 5 | -5 + | 8 | 8 | eight | | 0 | | 0 + | 8 | 8 | eight | | 0 | | + | 8 | 8 | eight | | | 0 | + | 8 | 8 | eight | | | 1 | -1 + | 8 | 8 | eight | | | 2 | 2 + | 8 | 8 | eight | | | 2 | 4 + | 8 | 8 | eight | | | 3 | -3 + | 8 | 8 | eight | | | 5 | -5 + | 8 | 8 | eight | | | 5 | -5 + | 8 | 8 | eight | | | | 0 + | 8 | 8 | eight | | | | + | | 0 | zero | 0 | | 0 | + | | 0 | zero | 0 | | 1 | -1 + | | 0 | zero | 0 | | 2 | 2 + | | 0 | zero | 0 | | 2 | 4 + | | 0 | zero | 0 | | 3 | -3 + | | 0 | zero | 0 | | 5 | -5 + | | 0 | zero | 0 | | 5 | -5 + | | 0 | zero | 0 | | | 0 + | | 0 | zero | 0 | | | + | | 0 | zero | 1 | -1 | 0 | + | | 0 | zero | 1 | -1 | 1 | -1 + | | 0 | zero | 1 | -1 | 2 | 2 + | | 0 | zero | 1 | -1 | 2 | 4 + | | 0 | zero | 1 | -1 | 3 | -3 + | | 0 | zero | 1 | -1 | 5 | -5 + | | 0 | zero | 1 | -1 | 5 | -5 + | | 0 | zero | 1 | -1 | | 0 + | | 0 | zero | 1 | -1 | | + | | 0 | zero | 2 | 2 | 0 | + | | 0 | zero | 2 | 2 | 1 | -1 + | | 0 | zero | 2 | 2 | 2 | 2 + | | 0 | zero | 2 | 2 | 2 | 4 + | | 0 | zero | 2 | 2 | 3 | -3 + | | 0 | zero | 2 | 2 | 5 | -5 + | | 0 | zero | 2 | 2 | 5 | -5 + | | 0 | zero | 2 | 2 | | 0 + | | 0 | zero | 2 | 2 | | + | | 0 | zero | 2 | 4 | 0 | + | | 0 | zero | 2 | 4 | 1 | -1 + | | 0 | zero | 2 | 4 | 2 | 2 + | | 0 | zero | 2 | 4 | 2 | 4 + | | 0 | zero | 2 | 4 | 3 | -3 + | | 0 | zero | 2 | 4 | 5 | -5 + | | 0 | zero | 2 | 4 | 5 | -5 + | | 0 | zero | 2 | 4 | | 0 + | | 0 | zero | 2 | 4 | | + | | 0 | zero | 3 | -3 | 0 | + | | 0 | zero | 3 | -3 | 1 | -1 + | | 0 | zero | 3 | -3 | 2 | 2 + | | 0 | zero | 3 | -3 | 2 | 4 + | | 0 | zero | 3 | -3 | 3 | -3 + | | 0 | zero | 3 | -3 | 5 | -5 + | | 0 | zero | 3 | -3 | 5 | -5 + | | 0 | zero | 3 | -3 | | 0 + | | 0 | zero | 3 | -3 | | + | | 0 | zero | 5 | -5 | 0 | + | | 0 | zero | 5 | -5 | 0 | + | | 0 | zero | 5 | -5 | 1 | -1 + | | 0 | zero | 5 | -5 | 1 | -1 + | | 0 | zero | 5 | -5 | 2 | 2 + | | 0 | zero | 5 | -5 | 2 | 2 + | | 0 | zero | 5 | -5 | 2 | 4 + | | 0 | zero | 5 | -5 | 2 | 4 + | | 0 | zero | 5 | -5 | 3 | -3 + | | 0 | zero | 5 | -5 | 3 | -3 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | | 0 + | | 0 | zero | 5 | -5 | | 0 + | | 0 | zero | 5 | -5 | | + | | 0 | zero | 5 | -5 | | + | | 0 | zero | | 0 | 0 | + | | 0 | zero | | 0 | 1 | -1 + | | 0 | zero | | 0 | 2 | 2 + | | 0 | zero | | 0 | 2 | 4 + | | 0 | zero | | 0 | 3 | -3 + | | 0 | zero | | 0 | 5 | -5 + | | 0 | zero | | 0 | 5 | -5 + | | 0 | zero | | 0 | | 0 + | | 0 | zero | | 0 | | + | | 0 | zero | | | 0 | + | | 0 | zero | | | 1 | -1 + | | 0 | zero | | | 2 | 2 + | | 0 | zero | | | 2 | 4 + | | 0 | zero | | | 3 | -3 + | | 0 | zero | | | 5 | -5 + | | 0 | zero | | | 5 | -5 + | | 0 | zero | | | | 0 + | | 0 | zero | | | | + | | | null | 0 | | 0 | + | | | null | 0 | | 1 | -1 + | | | null | 0 | | 2 | 2 + | | | null | 0 | | 2 | 4 + | | | null | 0 | | 3 | -3 + | | | null | 0 | | 5 | -5 + | | | null | 0 | | 5 | -5 + | | | null | 0 | | | 0 + | | | null | 0 | | | + | | | null | 1 | -1 | 0 | + | | | null | 1 | -1 | 1 | -1 + | | | null | 1 | -1 | 2 | 2 + | | | null | 1 | -1 | 2 | 4 + | | | null | 1 | -1 | 3 | -3 + | | | null | 1 | -1 | 5 | -5 + | | | null | 1 | -1 | 5 | -5 + | | | null | 1 | -1 | | 0 + | | | null | 1 | -1 | | + | | | null | 2 | 2 | 0 | + | | | null | 2 | 2 | 1 | -1 + | | | null | 2 | 2 | 2 | 2 + | | | null | 2 | 2 | 2 | 4 + | | | null | 2 | 2 | 3 | -3 + | | | null | 2 | 2 | 5 | -5 + | | | null | 2 | 2 | 5 | -5 + | | | null | 2 | 2 | | 0 + | | | null | 2 | 2 | | + | | | null | 2 | 4 | 0 | + | | | null | 2 | 4 | 1 | -1 + | | | null | 2 | 4 | 2 | 2 + | | | null | 2 | 4 | 2 | 4 + | | | null | 2 | 4 | 3 | -3 + | | | null | 2 | 4 | 5 | -5 + | | | null | 2 | 4 | 5 | -5 + | | | null | 2 | 4 | | 0 + | | | null | 2 | 4 | | + | | | null | 3 | -3 | 0 | + | | | null | 3 | -3 | 1 | -1 + | | | null | 3 | -3 | 2 | 2 + | | | null | 3 | -3 | 2 | 4 + | | | null | 3 | -3 | 3 | -3 + | | | null | 3 | -3 | 5 | -5 + | | | null | 3 | -3 | 5 | -5 + | | | null | 3 | -3 | | 0 + | | | null | 3 | -3 | | + | | | null | 5 | -5 | 0 | + | | | null | 5 | -5 | 0 | + | | | null | 5 | -5 | 1 | -1 + | | | null | 5 | -5 | 1 | -1 + | | | null | 5 | -5 | 2 | 2 + | | | null | 5 | -5 | 2 | 2 + | | | null | 5 | -5 | 2 | 4 + | | | null | 5 | -5 | 2 | 4 + | | | null | 5 | -5 | 3 | -3 + | | | null | 5 | -5 | 3 | -3 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | | 0 + | | | null | 5 | -5 | | 0 + | | | null | 5 | -5 | | + | | | null | 5 | -5 | | + | | | null | | 0 | 0 | + | | | null | | 0 | 1 | -1 + | | | null | | 0 | 2 | 2 + | | | null | | 0 | 2 | 4 + | | | null | | 0 | 3 | -3 + | | | null | | 0 | 5 | -5 + | | | null | | 0 | 5 | -5 + | | | null | | 0 | | 0 + | | | null | | 0 | | + | | | null | | | 0 | + | | | null | | | 1 | -1 + | | | null | | | 2 | 2 + | | | null | | | 2 | 4 + | | | null | | | 3 | -3 + | | | null | | | 5 | -5 + | | | null | | | 5 | -5 + | | | null | | | | 0 + | | | null | | | | +(891 rows) + -- -- -- Inner joins (equi-joins) @@ -170,20 +1584,57 @@ ERROR: Cross storage engine query is not supported SELECT '' AS "xxx", * FROM J1_TBL INNER JOIN J2_TBL USING (i) ORDER BY i, j, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + -- Same as above, slightly different syntax SELECT '' AS "xxx", * FROM J1_TBL JOIN J2_TBL USING (i) ORDER BY i, j, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a) ORDER BY a, d; -ERROR: Cross storage engine query is not supported + xxx | a | b | c | d +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b) ORDER BY b, t1.a; -ERROR: Cross storage engine query is not supported + xxx | b | a | c | a +-----+---+---+-------+--- + | 0 | 5 | five | + | 0 | | zero | + | 2 | 3 | three | 2 + | 4 | 1 | one | 2 +(4 rows) + -- -- NATURAL JOIN -- Inner equi-join on all columns with the same name @@ -191,39 +1642,103 @@ ERROR: Cross storage engine query is not supported SELECT '' AS "xxx", * FROM J1_TBL NATURAL JOIN J2_TBL ORDER BY i, j, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d) ORDER BY a, b, c, d; -ERROR: Cross storage engine query is not supported + xxx | a | b | c | d +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a) ORDER BY a, b, c, d; -ERROR: Cross storage engine query is not supported + xxx | a | b | c | d +-----+---+---+------+--- + | 0 | | zero | + | 2 | 3 | two | 2 + | 4 | 1 | four | 2 +(3 rows) + -- mismatch number of columns -- currently, Postgres will fill in with underlying names SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a) ORDER BY a, b, t, k; -ERROR: Cross storage engine query is not supported + xxx | a | b | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + -- -- Inner joins (equi-joins) -- SELECT '' AS "xxx", * FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i) ORDER BY J1_TBL.i, J1_TBL.j, J1_TBL.t, J2_TBL.i, J2_TBL.k; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | i | k +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 1 | 4 | one | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 +(7 rows) + SELECT '' AS "xxx", * FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k) ORDER BY J1_TBL.i, J1_TBL.j, J1_TBL.t, J2_TBL.i, J2_TBL.k; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | i | k +-----+---+---+------+---+--- + | 0 | | zero | | 0 + | 2 | 3 | two | 2 | 2 + | 4 | 1 | four | 2 | 4 +(3 rows) + -- -- Non-equi-joins -- SELECT '' AS "xxx", * FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k) ORDER BY J1_TBL.i, J1_TBL.j, J1_TBL.t, J2_TBL.i, J2_TBL.k; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | i | k +-----+---+---+-------+---+--- + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | | 0 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 3 | 2 | three | 2 | 4 + | 4 | 1 | four | 2 | 4 +(9 rows) + -- -- Outer joins -- Note that OUTER is a noise word @@ -231,33 +1746,132 @@ ERROR: Cross storage engine query is not supported SELECT '' AS "xxx", * FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i) ORDER BY i, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | null | + | | 0 | zero | +(13 rows) + SELECT '' AS "xxx", * FROM J1_TBL LEFT JOIN J2_TBL USING (i) ORDER BY i, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | null | + | | 0 | zero | +(13 rows) + SELECT '' AS "xxx", * FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i) ORDER BY i, j, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | | | | 0 + | | | | +(9 rows) + SELECT '' AS "xxx", * FROM J1_TBL RIGHT JOIN J2_TBL USING (i) ORDER BY i, j, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | | | | 0 + | | | | +(9 rows) + SELECT '' AS "xxx", * FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i) ORDER BY i, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | | 0 + | | | null | + | | 0 | zero | + | | | | +(15 rows) + SELECT '' AS "xxx", * FROM J1_TBL FULL JOIN J2_TBL USING (i) ORDER BY i, k, t; -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | | 0 + | | | null | + | | 0 | zero | + | | | | +(15 rows) + SELECT '' AS "xxx", * FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1); -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+---+--- +(0 rows) + SELECT '' AS "xxx", * FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1); -ERROR: Cross storage engine query is not supported + xxx | i | j | t | k +-----+---+---+-----+---- + | 1 | 4 | one | -1 +(1 row) + -- -- More complicated constructs -- @@ -282,7 +1896,14 @@ INSERT INTO t3 VALUES ( 'cc', 23 ); INSERT INTO t3 VALUES ( 'dd', 33 ); SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name) ORDER BY name,t1.n, t2.n, t3.n; -ERROR: Cross storage engine query is not supported + name | n | n | n +------+----+----+---- + bb | 11 | 12 | 13 + cc | | 22 | 23 + dd | | | 33 + ee | | 42 | +(4 rows) + -- -- Test interactions of join syntax and subqueries -- @@ -293,21 +1914,39 @@ INNER JOIN (SELECT * FROM t3) s3 USING (name) ORDER BY name, s2.n, s3.n; -ERROR: Cross storage engine query is not supported + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 +(2 rows) + SELECT * FROM (SELECT * FROM t2) as s2 LEFT JOIN (SELECT * FROM t3) s3 USING (name) ORDER BY name, s2.n, s3.n; -ERROR: Cross storage engine query is not supported + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 + ee | 42 | +(3 rows) + SELECT * FROM (SELECT * FROM t2) as s2 FULL JOIN (SELECT * FROM t3) s3 USING (name) ORDER BY name, s2.n, s3.n; -ERROR: Cross storage engine query is not supported + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 + dd | | 33 + ee | 42 | +(4 rows) + -- Cases with non-nullable expressions in subquery results; -- make sure these go to null as expected SELECT * FROM @@ -315,19 +1954,37 @@ SELECT * FROM NATURAL INNER JOIN (SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 ORDER BY name, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 +(2 rows) + SELECT * FROM (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 NATURAL LEFT JOIN (SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 ORDER BY name, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 + ee | 42 | 2 | | +(3 rows) + SELECT * FROM (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 NATURAL FULL JOIN (SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 ORDER BY name, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 + dd | | | 33 | 3 + ee | 42 | 2 | | +(4 rows) + SELECT * FROM (SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 NATURAL INNER JOIN @@ -335,7 +1992,11 @@ NATURAL INNER JOIN NATURAL INNER JOIN (SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 ORDER BY name, s1_n, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------+------+------ + bb | 11 | 1 | 12 | 2 | 13 | 3 +(1 row) + SELECT * FROM (SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 NATURAL FULL JOIN @@ -343,7 +2004,14 @@ NATURAL FULL JOIN NATURAL FULL JOIN (SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 ORDER BY name, s1_n, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------+------+------ + bb | 11 | 1 | 12 | 2 | 13 | 3 + cc | | | 22 | 2 | 23 | 3 + dd | | | | | 33 | 3 + ee | | | 42 | 2 | | +(4 rows) + SELECT * FROM (SELECT name, n as s1_n FROM t1) as s1 NATURAL FULL JOIN @@ -353,7 +2021,14 @@ NATURAL FULL JOIN (SELECT name, n as s3_n FROM t3) as s3 ) ss2 ORDER BY name, s1_n, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s1_n | s2_n | s3_n +------+------+------+------ + bb | 11 | 12 | 13 + cc | | 22 | 23 + dd | | | 33 + ee | | 42 | +(4 rows) + SELECT * FROM (SELECT name, n as s1_n FROM t1) as s1 NATURAL FULL JOIN @@ -363,7 +2038,14 @@ NATURAL FULL JOIN (SELECT name, n as s3_n FROM t3) as s3 ) ss2 ORDER BY name, s1_n, s2_n, s3_n; -ERROR: Cross storage engine query is not supported + name | s1_n | s2_n | s2_2 | s3_n +------+------+------+------+------ + bb | 11 | 12 | 2 | 13 + cc | | 22 | 2 | 23 + dd | | | | 33 + ee | | 42 | 2 | +(4 rows) + -- Test for propagation of nullability constraints into sub-joins drop foreign table x; ERROR: foreign table "x" does not exist @@ -400,34 +2082,100 @@ select * from y ORDER BY y1; (4 rows) select * from x left join y on (x1 = y1 and x2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 +----+----+----+----- + 1 | 11 | 1 | 111 + 2 | 22 | 2 | 222 + 3 | | | + 4 | 44 | 4 | + 5 | | | +(5 rows) + select * from x left join y on (x1 = y1 and y2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 +----+----+----+----- + 1 | 11 | 1 | 111 + 2 | 22 | 2 | 222 + 3 | | 3 | 333 + 4 | 44 | | + 5 | | | +(5 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | 5 | +(5 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1 and x2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | | +(5 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1 and y2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | + 4 | 44 | 4 | | | + 5 | | | | | +(5 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1 and xx2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | | +(5 rows) + -- these should NOT give the same answers as above select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1) where (x2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 4 | 44 | 4 | | 4 | 44 +(3 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1) where (y2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | +(3 rows) + select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) on (x1 = xx1) where (xx2 is not null) ORDER BY x1, x2, y1, y2; -ERROR: Cross storage engine query is not supported + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 4 | 44 | 4 | | 4 | 44 +(3 rows) + -- -- Clean up -- @@ -458,14 +2206,12 @@ SELECT * FROM t3 ORDER By x, y; (3 rows) DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a; -ERROR: Cross storage engine query is not supported SELECT * FROM t3 ORDER By x, y; - x | y ------+----- - 6 | 7 - 7 | 8 - 500 | 100 -(3 rows) + x | y +---+--- + 6 | 7 + 7 | 8 +(2 rows) DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y; SELECT * FROM t3 ORDER By x, y; @@ -474,14 +2220,15 @@ SELECT * FROM t3 ORDER By x, y; (0 rows) select * from (select * from t1 where t1.a in (1,4,11) union all select * from t2) as dt order by 1; -ERROR: Cross storage engine query is not supported + a | b +-----+------ + 200 | 2000 +(1 row) + update t1 set a =1 where b in (select b from t1 where t1.a in (1,4,11) union all select b from t2); -ERROR: Cross storage engine query is not supported update t1 set a =1 where b in (select b from t1 where t1.a in (1,4,11) union all select b from t3); delete t1 where b in (select b from t1 where t1.a in (1,4,11) union all select b from t2); -ERROR: Cross storage engine query is not supported delete t1 where b in (select b from t2 where t2.a in (1,4,11) union all select b from t3); -ERROR: Cross storage engine query is not supported delete t1 where b in (select b from t1 where t1.a in (1,4,11) union all select b from t3); select a from (select a from t2); a @@ -499,7 +2246,11 @@ select a , (select a from t3) from (select a from t1); (4 rows) select a , (select a from t3) from (select a from t2); -ERROR: Cross storage engine query is not supported + a | a +-----+--- + 200 | +(1 row) + select a , (select a from t2) from (select a from t2); a | a -----+----- @@ -525,7 +2276,10 @@ CREATE TABLE y5 (a int, y int); select y5.a , (select (select y1.b from y1, y2) from (select y3.a from y3,y4)) from y5; -ERROR: Cross storage engine query is not supported + a | b +---+--- +(0 rows) + select y5.a , (select (select y1.b from y1, y2) from (select y3.a from y3,y2)) from y5; @@ -542,26 +2296,24 @@ from y5; select y5.a ,(select (select y1.b from y1, y2 where y1.a in (select y4.a from y4 )) from (select y3.a from y3,y2)) from y5; -ERROR: Cross storage engine query is not supported + a | b +---+--- +(0 rows) + -- cross transaction check begin; insert into y1 values (1); insert into y4 values (1); -ERROR: Cross storage engine transaction is not supported end; begin; delete from y1 ; delete from y4; -ERROR: Cross storage engine transaction is not supported end; begin; update y1 set a =1; update y4 set a =2; -ERROR: Cross storage engine transaction is not supported end; begin; update y1 set a =1 where a in (select a from y4); -ERROR: Cross storage engine query is not supported update y4 set a =2; -ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] end; diff --git a/src/test/regress/expected/mot/single_merge_privilege.out b/src/test/regress/expected/mot/single_merge_privilege.out index a89be49c6..1968a6312 100644 --- a/src/test/regress/expected/mot/single_merge_privilege.out +++ b/src/test/regress/expected/mot/single_merge_privilege.out @@ -9,15 +9,19 @@ NOTICE: The encrypted password contains MD5 ciphertext, which is not secure. CREATE FOREIGN TABLE merge_privs_target (id integer, balance integer) SERVER mot_server ; CREATE FOREIGN TABLE merge_privs_source (id integer, delta integer) SERVER mot_server ; ALTER TABLE merge_privs_target OWNER TO merge_privs; -ERROR: "merge_privs_target" is a mot, which does not support alter table. +ERROR: "merge_privs_target" is not a table +HINT: Use ALTER FOREIGN TABLE to alter a foreign table. ALTER TABLE merge_privs_source OWNER TO merge_privs; -ERROR: "merge_privs_source" is a mot, which does not support alter table. +ERROR: "merge_privs_source" is not a table +HINT: Use ALTER FOREIGN TABLE to alter a foreign table. CREATE FOREIGN TABLE merge_privs_source2 (id integer, delta integer) SERVER mot_server ; CREATE FOREIGN TABLE merge_privs_target2 (id integer, balance integer) SERVER mot_server ; ALTER TABLE merge_privs_target2 OWNER TO merge_no_privs; -ERROR: "merge_privs_target2" is a mot, which does not support alter table. +ERROR: "merge_privs_target2" is not a table +HINT: Use ALTER FOREIGN TABLE to alter a foreign table. ALTER TABLE merge_privs_source2 OWNER TO merge_no_privs; -ERROR: "merge_privs_source2" is a mot, which does not support alter table. +ERROR: "merge_privs_source2" is not a table +HINT: Use ALTER FOREIGN TABLE to alter a foreign table. -- merge_no_privs has INSERT permission for merge_privs_target, but has no SELECT permission for merge_privs_target GRANT INSERT ON merge_privs_target TO merge_no_privs; SET SESSION AUTHORIZATION merge_no_privs PASSWORD 'merge@123'; diff --git a/src/test/regress/expected/mot/single_new_indexes.out b/src/test/regress/expected/mot/single_new_indexes.out new file mode 100644 index 000000000..89524c8e3 --- /dev/null +++ b/src/test/regress/expected/mot/single_new_indexes.out @@ -0,0 +1,234 @@ +-- BUG 92 +create foreign table test_new_index (x float4 not null, y float8 not null, z varchar(1) not null); +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); +insert into test_new_index values (1,1,'1'); +select * from test_new_index where z = 49; + x | y | z +---+---+--- +(0 rows) + +-- CRASH +drop foreign table test_new_index; +-- BUG 91 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); +insert into test_new_index (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +delete from test_new_index where y > x; +drop foreign table test_new_index; +-- BUG 90 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); +insert into test_new_index (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +select * from test_new_index where x = 9; + x | y | z +---+---+--- +(0 rows) + +drop foreign table test_new_index; +-- BUG 89 +create foreign table test_new_index (i integer not null, i2 int2 not null, i4 int4 not null, i8 int8 not null, n numeric not null, f4 float4 not null, f8 float8 not null, s serial not null, c char not null, v varchar(10) not null, vchar varchar(1020) not null, d date not null, t time not null, ts timestamp not null, intrvl interval not null, b boolean not null); +NOTICE: CREATE FOREIGN TABLE will create implicit sequence "test_new_index_s_seq" for serial column "test_new_index.s" +create index idx1 on test_new_index (i); +create index idx2 on test_new_index (i2); +create index idx3 on test_new_index (i4); +create index idx4 on test_new_index (i8); +create index idx5 on test_new_index (i,i2,i4); +create index idx6 on test_new_index (f4,f8); +create index idx7 on test_new_index (c,v); +create index idx8 on test_new_index (d,t,ts); +create index idx9 on test_new_index (intrvl); +create index idx10 on test_new_index (b); +ERROR: Can not create index, max number of indexes 10 reached +insert into test_new_index (i, i2, i4) values (generate_series(1,100), generate_series(1,100), generate_series(1,100)); +ERROR: null value in column "i8" violates not-null constraint +DETAIL: Failing row contains (1, 1, 1, null, null, null, null, 1, null, null, null, null, null, null, null, null). +select * from test_new_index order by i limit 10; + i | i2 | i4 | i8 | n | f4 | f8 | s | c | v | vchar | d | t | ts | intrvl | b +---+----+----+----+---+----+----+---+---+---+-------+---+---+----+--------+--- +(0 rows) + +drop foreign table test_new_index; +-- BUG 87 +create foreign table test_new_index (f1 float4 not null, f2 float8 not null, primary key (f1, f2)) ; +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx1 on test_new_index (f1); +create index idx2 on test_new_index (f2); +insert into test_new_index values (0.0, 0.1); +insert into test_new_index values (0.0, -0.1); +insert into test_new_index values (-0.10, 0.1); +insert into test_new_index values (0.101, 0.1); +insert into test_new_index values (1.0, 0.1); +insert into test_new_index values (-1.10000001, 0.1); +select * from test_new_index order by f1; + f1 | f2 +------+----- + -1.1 | .1 + -.1 | .1 + 0 | -.1 + 0 | .1 + .101 | .1 + 1 | .1 +(6 rows) + +select * from test_new_index where f1 > -10.2 order by f1, f2; + f1 | f2 +------+----- + -1.1 | .1 + -.1 | .1 + 0 | -.1 + 0 | .1 + .101 | .1 + 1 | .1 +(6 rows) + +drop foreign table test_new_index; +-- BUG 86 +create foreign table test_new_index (v1 varchar(4) not null, v2 varchar(4) not null, primary key (v1, v2)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx on test_new_index (v1); +create index idx2 on test_new_index (v2); +insert into test_new_index values ('aaaa','aaaa'); +insert into test_new_index values ('aaaa','bbbb'); +insert into test_new_index values ('bbbb','bbbb'); +insert into test_new_index values ('bbbb','aaaa'); +select * from test_new_index order by 1,2; + v1 | v2 +------+------ + aaaa | aaaa + aaaa | bbbb + bbbb | aaaa + bbbb | bbbb +(4 rows) + +select * from test_new_index where v1 = 'aaa'; + v1 | v2 +----+---- +(0 rows) + +select * from test_new_index where v1 = 'bb'; + v1 | v2 +----+---- +(0 rows) + +drop foreign table test_new_index; +-- BUG 85 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create unique index udx1 on test_new_index (x,y,z); +create index udx1 on test_new_index (x,y,z); +ERROR: relation "udx1" already exists +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); +insert into test_new_index values (1,2,3); +insert into test_new_index values (2,3,4); +insert into test_new_index values (3,4,5); +insert into test_new_index values (4,5,6); +insert into test_new_index values (5,6,7); +insert into test_new_index values (6,7,8); +select * from test_new_index where x > 4 and x = 5 order by x,y,z; + x | y | z +---+---+--- + 5 | 6 | 7 +(1 row) + +drop foreign table test_new_index; +-- BUG 84 +CREATE FOREIGN TABLE func_index_heap (f1 varchar(100) not null, f2 varchar(100) not null) ; +CREATE UNIQUE INDEX func_index_index on func_index_heap ((f1 || f2) text_ops); +ERROR: Can't create index on field +DETAIL: Expressions are not supported +drop foreign table func_index_heap; +drop index func_index_index; +ERROR: index "func_index_index" does not exist +-- BUG 83 +CREATE FOREIGN TABLE func_index_heap (f1 varchar(100) not null, f2 varchar(100) not null); +CREATE UNIQUE INDEX func_index_index on func_index_heap (textcat(f1,f2)); +ERROR: Can't create index on field +DETAIL: Expressions are not supported +drop foreign table func_index_heap; +drop index func_index_index; +ERROR: index "func_index_index" does not exist +-- BUG 82 +create foreign table test_new_index (i int not null, i2 int2 not null, i4 int4 not null, i8 int8 not null, primary key (i)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx1 on test_new_index (i, i2, i4, i8); +insert into test_new_index values (generate_series(1,100), generate_series(1,100), generate_series(1,100), generate_series(1,100)); +select * from test_new_index where i = i2 and i2 = i4 and i4 = i8 and i8 = i order by i,i2,i4,i8 limit 10; + i | i2 | i4 | i8 +----+----+----+---- + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 4 | 4 + 5 | 5 | 5 | 5 + 6 | 6 | 6 | 6 + 7 | 7 | 7 | 7 + 8 | 8 | 8 | 8 + 9 | 9 | 9 | 9 + 10 | 10 | 10 | 10 +(10 rows) + +drop foreign table test_new_index; +-- BUG 81 +create foreign table test_new_index (v1 varchar(1) not null, v2 varchar(1) not null, primary key (v1, v2)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_index_pkey" for foreign table "test_new_index" +create index idx on test_new_index (v1); +create index idx2 on test_new_index (v2); +insert into test_new_index values ('a','a'); +insert into test_new_index values ('a','b'); +insert into test_new_index values ('b','b'); +insert into test_new_index values ('b','a'); +select * from test_new_index order by 1,2; + v1 | v2 +----+---- + a | a + a | b + b | a + b | b +(4 rows) + +select * from test_new_index where v1 = 'a' order by v1,v2; + v1 | v2 +----+---- + a | a + a | b +(2 rows) + +select * from test_new_index where v1 = 'ab'; + v1 | v2 +----+---- +(0 rows) + +select * from test_new_index where v1 = 'abc'; + v1 | v2 +----+---- +(0 rows) + +select * from test_new_index where v1 = 'abcd'; + v1 | v2 +----+---- +(0 rows) + +drop foreign table test_new_index; +--BUG in MAX_KEY_LEN +create foreign table test_new_index (i varchar(8) not null, j varchar(244) not null); +create index tx on test_new_index (j); +create index tx2 on test_new_index (i); +create index tx3 on test_new_index (i,j); +ERROR: Can't create index +DETAIL: Total columns size is greater than maximum index size 256 +create index tx4 on test_new_index (j,i); +ERROR: Can't create index +DETAIL: Total columns size is greater than maximum index size 256 +create index tx7 on test_new_index (j); +create index tx8 on test_new_index (i); +drop foreign table test_new_index;-- diff --git a/src/test/regress/expected/mot/single_new_indexes2.out b/src/test/regress/expected/mot/single_new_indexes2.out new file mode 100644 index 000000000..66a09bde3 --- /dev/null +++ b/src/test/regress/expected/mot/single_new_indexes2.out @@ -0,0 +1,224 @@ +-- BUG 94 +create foreign table test_new2 (x int primary key, y int not null, z int not null); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new2_pkey" for foreign table "test_new2" +-- create duplicates +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +ERROR: duplicate key value violates unique constraint "test_new2_pkey" +DETAIL: Key (x)=(10) already exists. +create index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (x); +select * from test_new2 where x > 0 order by x; + x | y | z +----+----+---- + 10 | 10 | 10 + 11 | 11 | 11 + 12 | 12 | 12 + 13 | 13 | 13 + 14 | 14 | 14 + 15 | 15 | 15 + 16 | 16 | 16 + 17 | 17 | 17 + 18 | 18 | 18 + 19 | 19 | 19 + 20 | 20 | 20 +(11 rows) + +drop foreign table test_new2; +-- BUG 95 +create foreign table test_new2 (x int not null, y int not null, z int not null); +create index idx1 on test_new2 (x); +create index idx2 on test_new2 (y); +create index idx3 on test_new2 (z); +create unique index uidx on test_new2 (z,x); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +truncate test_new2; +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +drop foreign table test_new2; +-- BUG 97 +create foreign table test_new2 (x int not null, y int not null, z int not null); +create index idx1 on test_new2 (x); +create index idx2 on test_new2 (y); +create index idx3 on test_new2 (z); +insert into test_new2 values (1,1,1); +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +drop foreign table test_new2; +-- BUG 98 +create foreign table test_new2 (x int, y int, z int, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new2_pkey" for foreign table "test_new2" +insert into test_new2 values (1,2,3); +insert into test_new2 values (2,3,4); +insert into test_new2 values (3,4,5); +insert into test_new2 values (4,5,6); +insert into test_new2 values (5,6,7); +insert into test_new2 values (6,7,8); +select * from test_new2 where x = y = z; + x | y | z +---+---+--- +(0 rows) + +drop foreign table test_new2; +-- TEST 1 +create foreign table test_new2 (x varchar(16), y varchar(16) not null, z varchar(16), primary key(x,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new2_pkey" for foreign table "test_new2" +create index idx1 on test_new2 (y); +insert into test_new2 values ('aa', 'aa', 'zz'); +insert into test_new2 values ('zz', 'aa', 'aa'); +insert into test_new2 values ('xxx', 'aa', 'tttt'); +insert into test_new2 values ('ii', 'aa', 'eee'); +insert into test_new2 values ('eeee', 'aa', 'iii'); +drop foreign table test_new2; +-- TEST 2 +create foreign table test_new2 (x int not null, y int, z int); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +create index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (x); +select * from test_new2 where x > 0 order by x; + x | y | z +----+----+---- + 10 | 10 | 10 + 11 | 11 | 11 + 12 | 12 | 12 + 13 | 13 | 13 + 14 | 14 | 14 + 15 | 15 | 15 + 16 | 16 | 16 + 17 | 17 | 17 + 18 | 18 | 18 + 19 | 19 | 19 + 20 | 20 | 20 +(11 rows) + +drop foreign table test_new2; +-- +-- TEST 3 +create foreign table test_new2 (x int not null, y int not null, z int not null, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new2_pkey" for foreign table "test_new2" +create unique index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (y); +create unique index idx3 on test_new2 (z); +insert into test_new2 values (1,2,3); +insert into test_new2 values (2,3,4); +insert into test_new2 values (3,4,5); +insert into test_new2 values (4,5,6); +insert into test_new2 values (5,6,7); +insert into test_new2 values (6,7,8); +select * from test_new2 where x < 0 order by z desc; + x | y | z +---+---+--- +(0 rows) + +select * from test_new2 where x <> 0 order by z desc; + x | y | z +---+---+--- + 6 | 7 | 8 + 5 | 6 | 7 + 4 | 5 | 6 + 3 | 4 | 5 + 2 | 3 | 4 + 1 | 2 | 3 +(6 rows) + +insert into test_new2 values (-4,-5,-6); +select * from test_new2 where x < 0 order by z desc; + x | y | z +----+----+---- + -4 | -5 | -6 +(1 row) + +select * from test_new2 where x <> 0 order by z desc; + x | y | z +----+----+---- + 6 | 7 | 8 + 5 | 6 | 7 + 4 | 5 | 6 + 3 | 4 | 5 + 2 | 3 | 4 + 1 | 2 | 3 + -4 | -5 | -6 +(7 rows) + +insert into test_new2 values (0,-5,-6); +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (y)=(-5) already exists. +insert into test_new2 values (0,100,100); +select * from test_new2 where x <> 0 order by z ; + x | y | z +----+----+---- + -4 | -5 | -6 + 1 | 2 | 3 + 2 | 3 | 4 + 3 | 4 | 5 + 4 | 5 | 6 + 5 | 6 | 7 + 6 | 7 | 8 +(7 rows) + +select * from test_new2 where x >= 0 and y <> 2 order by z ; + x | y | z +---+-----+----- + 2 | 3 | 4 + 3 | 4 | 5 + 4 | 5 | 6 + 5 | 6 | 7 + 6 | 7 | 8 + 0 | 100 | 100 +(6 rows) + +drop foreign table test_new2; +-- TEST 4 +create foreign table test_new21 (x int, y int, z int, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new21_pkey" for foreign table "test_new21" +create foreign table test_new22 (x int, y int, z int, primary key(x,y,z)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new22_pkey" for foreign table "test_new22" +insert into test_new21 (y,x,z) values (generate_series(10,100), generate_series(10,100), generate_series(10,100)); +insert into test_new22 select * from test_new21; +truncate test_new21; +insert into test_new21 select * from test_new22; +truncate test_new22; +drop foreign table test_new21; +drop foreign table test_new22; +-- TEST 5 +create foreign table test_new2 (x int not null, y int not null, z int not null) ; +create unique index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (y); +create unique index idx3 on test_new2 (z); +insert into test_new2 values (1,1,1); +insert into test_new2 (x,y,z) values (2,2,2); +insert into test_new2 (x) values (generate_series(10,20)); +ERROR: null value in column "y" violates not-null constraint +DETAIL: Failing row contains (10, null, null). +select * from test_new2; + x | y | z +---+---+--- + 1 | 1 | 1 + 2 | 2 | 2 +(2 rows) + +drop foreign table test_new2; +-- BUG 153 +CREATE FOREIGN TABLE MATERIAL_BATCH_1_002_1( +C_CHAR_1 CHAR(1) not null, +C_CHAR_2 CHAR(10) not null, +C_CHAR_3 CHAR(100) not null, +C_VARCHAR_1 VARCHAR(1) not null, +C_VARCHAR_2 VARCHAR(10) not null, +C_VARCHAR_3 VARCHAR(64) not null, +C_INT BIGINT not null, +C_BIGINT BIGINT not null, +C_SMALLINT BIGINT not null, +C_FLOAT FLOAT not null, +C_NUMERIC numeric(20,5) not null, +C_DP double precision not null, +C_DATE DATE not null, +C_TS_WITHOUT TIMESTAMP not null, +C_TS_WITH TIMESTAMP not null); +CREATE INDEX MATERIAL_INDEX_002_1 ON MATERIAL_BATCH_1_002_1(C_CHAR_1,C_CHAR_2,C_CHAR_3,C_VARCHAR_1,C_VARCHAR_2,C_VARCHAR_3,C_INT,C_BIGINT,C_TS_WITH); +CREATE INDEX MATERIAL_INDEX_002_2 ON MATERIAL_BATCH_1_002_1(C_CHAR_1,C_CHAR_2,C_CHAR_3,C_VARCHAR_1,C_VARCHAR_2,C_VARCHAR_3,C_INT,C_BIGINT,C_TS_WITH,C_SMALLINT); +DROP FOREIGN TABLE MATERIAL_BATCH_1_002_1; +-- diff --git a/src/test/regress/expected/mot/single_new_indexes3.out b/src/test/regress/expected/mot/single_new_indexes3.out new file mode 100644 index 000000000..b9d3813e0 --- /dev/null +++ b/src/test/regress/expected/mot/single_new_indexes3.out @@ -0,0 +1,949 @@ +--T0 +drop foreign table test_new_gc; +ERROR: foreign table "test_new_gc" does not exist +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z,data) values (1,2,3,0); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +update test_new_gc set data=100 where z=3; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +commit; +begin; +update test_new_gc set data=200 where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +commit; +--T1 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z,data) values (1,2,3,0); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 0 +(1 row) + +update test_new_gc set data=100 where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +commit; +begin; +update test_new_gc set data=200 where y=2; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 200 +(1 row) + +commit; +--T2 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +select * from test_new_gc where x=1; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +select * from test_new_gc where y=2; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +delete from test_new_gc where z=3; +select * from test_new_gc where x=1; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where y=2; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- +(0 rows) + +commit; +select * from test_new_gc where x=1; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where y=2; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- +(0 rows) + +--T3 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +select * from test_new_gc where x=1; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +select * from test_new_gc where y=2; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- + 1 | 2 | 3 +(1 row) + +delete from test_new_gc where z=3; +select * from test_new_gc where x=1; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where y=2; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- +(0 rows) + +commit; +--T4 Duplicates +delete from test_new_gc; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (1,2,3); +ERROR: duplicate key value violates unique constraint "test_new_gc_pkey" +DETAIL: Key (x)=(1) already exists. +--T5 Blocked Duplicates +delete from test_new_gc; +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (1,2,3); +ERROR: duplicate key value violates unique constraint "test_new_gc_pkey" +DETAIL: Key (x)=(1) already exists. +commit; +--T5.1 unique index duplicates! +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (2,3,3); +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (z)=(3) already exists. +--T5.2 unique index duplicates! +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (2,3,3); +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (z)=(3) already exists. +commit; +--T6 Insert-Delete-Insert All in A block +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +commit; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +--T6.1 Insert-Delete-Insert All in A block - unique index +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +commit; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +--T7 Insert-Delete-Insert All in A block II +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,88); +commit; +select * from test_new_gc where x=1; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +select * from test_new_gc where y=2; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +select * from test_new_gc where z=88; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +--T7.1 Insert-Delete-Insert All in A block II - unique +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,88); +commit; +select * from test_new_gc where x=1; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +select * from test_new_gc where y=2; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +select * from test_new_gc where z=3; + x | y | z +---+---+--- +(0 rows) + +select * from test_new_gc where z=88; + x | y | z +---+---+---- + 1 | 2 | 88 +(1 row) + +--New Delete Design +--T8 Insert-Begin-Delete-Insert-Rollback +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +rollback; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +--T8.1 Insert-Begin-Delete-Insert-Rollback - unique +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +rollback; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | +(1 row) + +--T9 Insert-Begin-Delete-Insert-Rollback +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +delete from test_new_gc where y=2; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,5); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=5; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +delete from test_new_gc where z=5; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=5; + x | y | z | data +---+---+---+------ +(0 rows) + +commit; +--T10 +drop foreign table test_new; +ERROR: foreign table "test_new" does not exist +create foreign table test_new (i int primary key, x int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_pkey" for foreign table "test_new" +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +select * from test_new where i = 3; + i | x +---+--- +(0 rows) + +commit; +--T11 +drop foreign table test_new; +create foreign table test_new (i int , x int); +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where x % 3 = 0; +insert into test_new values (3,333); +end; +drop foreign table test_new; +--T12 Inserts Tests +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +commit; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +--T13 Inserts Tests II +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_gc_pkey" for foreign table "test_new_gc" +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | +(1 row) + +delete from test_new_gc where y=2; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,5); +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=5; + x | y | z | data +---+---+---+------ + 1 | 2 | 5 | +(1 row) + +delete from test_new_gc where z=5; +select * from test_new_gc where x=1; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=5; + x | y | z | data +---+---+---+------ +(0 rows) + +insert into test_new_gc (x,y,z) values (1,2,100); +commit; +select * from test_new_gc where x=1; + x | y | z | data +---+---+-----+------ + 1 | 2 | 100 | +(1 row) + +select * from test_new_gc where y=2; + x | y | z | data +---+---+-----+------ + 1 | 2 | 100 | +(1 row) + +select * from test_new_gc where z=3; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=4; + x | y | z | data +---+---+---+------ +(0 rows) + +select * from test_new_gc where z=100; + x | y | z | data +---+---+-----+------ + 1 | 2 | 100 | +(1 row) + +drop foreign table test_new_gc; +-- bug 150 +create foreign table test_new (x integer primary key, y integer not null, c1 varchar(1020), c2 varchar(1020)) ; +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_pkey" for foreign table "test_new" +create index idx on test_new (x,y); +begin; +insert into test_new values (generate_series(1, 500), generate_series(1, 500)); +delete from test_new where x = 15; +insert into test_new values (15,15); +insert into test_new values (15,15); +ERROR: duplicate key value violates unique constraint "test_new_pkey" +DETAIL: Key (x)=(15) already exists. +end; +drop foreign table test_new; +-- bug 170 +create foreign table test_new (i int primary key, x int not null); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_pkey" for foreign table "test_new" +insert into test_new values (generate_series(1,100)); +ERROR: null value in column "x" violates not-null constraint +DETAIL: Failing row contains (1, null). +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; +begin; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; +drop foreign table test_new; +-- bug 169 +create foreign table test_new (i int primary key, x int not null); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_new_pkey" for foreign table "test_new" +insert into test_new values (generate_series(1,100)); +ERROR: null value in column "x" violates not-null constraint +DETAIL: Failing row contains (1, null). +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; +delete from test_new where i = 3; +drop foreign table test_new; diff --git a/src/test/regress/expected/mot/single_sp_queries_rtd.out b/src/test/regress/expected/mot/single_sp_queries_rtd.out new file mode 100644 index 000000000..0afea9775 --- /dev/null +++ b/src/test/regress/expected/mot/single_sp_queries_rtd.out @@ -0,0 +1,271 @@ +create foreign table customer ( +c_w_id integer not null, +c_d_id integer not null, +c_id integer not null, +c_discount decimal(4,4), +c_credit char(2), +c_last varchar(16) not null, +c_first varchar(16) not null, +c_credit_lim decimal(12,2), +c_balance decimal(12,2), +c_ytd_payment decimal(12,2), +c_payment_cnt integer, +c_delivery_cnt integer, +c_street_1 varchar(20), +c_street_2 varchar(20), +c_city varchar(20), +c_state char(2), +c_zip char(9), +c_phone char(16), +c_since timestamp, +c_middle char(2), +c_data varchar(500), +primary key (c_w_id, c_d_id, c_id) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "customer_pkey" for foreign table "customer" +insert into customer values(1,1,1,.4819,'GC','BARBARBAR','K1qXJYUx8',50000.00,-10.00,10.00,1,1,'GzRriVYf8PkgzfDWHo','hw5XQWK43NYR','S4qUZfkIBvp7HIgdYEPc','RF',637211111,3140408377069331,'2021-05-03 18:17:11.558','OE','qJ4LHCniHIiv5dnReoE36YzJnndwpSTXp2VWCPcVQYJ2SZxJ6RfQAt6zs3wwyl3rEDeoo5UEAzZKndJaqaU5I27X1u0jNUUXGtnM24Q9i4L3rY9ysXzokgB0eReIBPHmcizS7qtULcIy8cyD3FrmsvVgABPeocvNSwltSxLkXm0G9yI2Z9lTmWQomiDWoYDGQtCKxxqwRFGz3KeKf8mh1HicQSPL57wP3cC5SK3JPjhRMlldWrUzthBa6orKIJhpHkz4UrJCvce71o9lWE0DezPuMQZvVs3vHey3E9D8my9ZhvnphCROC8qJ2pIQ7Jv0asc5kUCFPkOrLz9KOmYnsP80KFVy377AMjuFTnTtXCExxDPjCJaJf1exzgfcBGLMxDjcw1HX7f4beI10GyX2P2KOuE'); +insert into customer values(1,1,2,.1120,'GC','BARBAROUGHT','KhZaT7ZZgZoiU',50000.00,-10.00,10.00,1,1,'EhbkYYG1wX8CJ1sqFQn','ibBtwABlISHbmIS36Nq','zrj4DcMHmbwQF','FZ',735311111,2391423312654894,'2021-05-03 18:17:11.558','OE','Su57rgcoXVY8gVIYH68miuTOIsiMXMNuTh0AwX5lc3CVm7U5x1UJS6cqVypHanbxLmwbf9ru5tt2g2GzORMhojJW4CchtVlSCBor6D3ZO32vIVUmwpOtgV7ZfHsapanWZHlXPTf3it5fkUCctrAZZ5bCuxFCFq1HjDuGGQ3YfAap8NKyPGWdlZI4dexOjuQGHSGwaOV97WR1YhaahDG4Y4z3EdmLPkIutg5ATjDFFAYBIJmWPfx1scIEZJANfO2LMsJuORNA1pY5RcBgIE528LO4nji8QrGPuiIUgsftepu2YHLSD5OjEo3xiNotRJAwkB'); +insert into customer values(1,1,3,.4651,'BC','BARBARABLE','IeE0f3Mp',50000.00,-10.00,10.00,1,1,'QTCUXZXNkbyJE','ix2pRoBHnO','pk7FNxuq9mc','OG',818211111,9505990784961970,'2021-05-03 18:17:11.558','OE','YYp3gkM6KDTE9DjLmWoxUqYQzBYV9YGQx8OzAGd0JjAvujjvzdrA7jfpYRJHuIqGKHuNlzXT1Tl7wZfJknuUXfZUfohIJ3lxPMQxzt2xMKlNXweJ28bbfR5nWbLGnQviVAotqce8KTGrqyVCN38M7hibGRbogKa55t3Wu7XePi1Mghllws62kAKgvC9DaWojjOcFXXnKI1bZgMjoDeVxZFATn7tCBVIVDh8kUR7SkJ1ZYjykbH9Iyi62vL497aOeqjRRa8dSoYNdgwxcUv891p1KyVgSGAMU8IfBnMyfG5wB0Ow8Rfkr9XedEk4B7R03bNhFUAfVF7L81z9EjFSc44SKwpovXBMGubxQz7fnEc9JDbCd2KIGpetqoeqwkmkAHyCbGfJP8GZGZoioTmLNNasvsotbRh7S1sqM2mI4qtRHOoJ9mKfY1WW5Ui2wMxWKQNI10s7VovXfSfgO'); +insert into customer values(1,1,4,.0141,'GC','BARBARPRI','XmyH4CJm3GTVRan',50000.00,-10.00,10.00,1,1,'DDgvLy5wBLRh5zX7ru2','LxqcrPJyQO8U','ogPkYtXVquW0N','FU',552811111,9444908480678354,'2021-05-03 18:17:11.558','OE','pF8XO8imui000TS00FSydSJzZAxPzJXSpTFjnJLlXF1C8rYY8QXtNyNN9ouRQScd80tbuAtcSYTEQPgxUgd9u79e69qBlPDDYGTISqBwG90OTEzFrakWyzySTASQeIRYbe3VPgiuRcMwybGoipZLbDHNixlGSZZCeXkFEempaPDaDBIqtrdnq6LAZAaDN18wCgZjMAv2B7EGteg2UtdQswOFOwGbnwG4VsMbp7axJsM74ZlS4E92MllOZnWD0cHyGk6KG7wRqzQlQNmi1cQix9YiG6j813WqktJw629RmwwmpqTNJ7TAERKuRUItksVl3Hvx1lgloX7V5WfbHyaHd57i2kIqMY8zeoAacD0DEICzugH7yynutInaTpZovuPUADs7NdVy393HDLaKQpq1ZzYnP9hxLmDhU9zjs7a'); +insert into customer values(1,1,5,.1251,'GC','BARBARPRES','WEHHK7ZdvakFPwa',50000.00,-10.00,10.00,1,1,'WnrnoQKSED','GTFxzPEcua66HQVmeuWp','cxsKcj4zQEb3xnurFcz','EY',581311111,0980067796148934,'2021-05-03 18:17:11.558','OE','ckgfjVSUJgXdkTjOmAhfnDlUI3TWt3AMnxTu9BUlPan1c5GCRo4NQKDX9H1lCX312ZKGqjCHKWtPKgL1uDDw1xnbrK2xePuaLBd92w44ZrWX2F6PvnDyR0yPZqaAmuFNLxd7KKbWJp57RWQSZ1qrTfB1FXECeHCPWtpSZX2bL9ziWqXS1LQ7OUGo1PmG9CnlRRVK93Qh6wvCY9ruONB2mf1q0CH0kOL9IDdvNBrN5kfFND2ivFfaZbEGD8SaIaDmj41y9B2cOzy9rALeNMUURnhEnu7A8XhRpQcW0lzBdSishHC4iHMaFrgnYw3cYUJnPjBt9n6JxIKOQx4u3qHWytXIco4PLoD4aN7z4ERYRxXDayTqgg4Yj0cpP1EmkstnXcYj6JDzgkCCM4Nofn5g5BS1vxVGXBAyYwooQ2XagDXfDdjhLXB5dL379BUFOlNMIatk'); +insert into customer values(1,1,6,.2541,'GC','BARBARESE','ZoeeBnnU1Zop6ML',50000.00,-10.00,10.00,1,1,'tGTUqTtjP1P0W','yUty9WfLIrslZUX','XpkIRU7D1rOt9ev95V6','NJ',093311111,0381242010918520,'2021-05-03 18:17:11.558','OE','KBRveK8nbwgb87VuhY9T2YsQoBEemGvuwQisgsXk8iJuVFwvF75aL0SOQi0iGIOHIDUAvznb5QVmzq4sY7cAG5gElx8DXvYa1lA3qMVDUSBtdgTYNxWchbjGbMuXOQdqOylH3Bo9CvDyoaLvyjg2KvkzRheGpTgaJFCEq4eKBVxsIt7biiqzoPvDXIuqcMpuYjcT86UtwOuuJRRWuEliL7ehviiY6WhqEc7TX6zORSpJ3hCwsaMRLnurwZFcFGHxUxfboDB91nm7N5ydJROzUz5qmafczmw5SPEONkkuiPnEeffMtKCgyXqjR2FIjSaXXWEyHGdkoDUd3yNX3PgQxFK8vIiLEIGNl5CrHnoDZE41v9WEAVi4MqesWdqcbguxdohuAXQhH4aZRNJLoQb2o8X5bQJccTLqzEVZVa58ZePzeTG6NUvDxVMSQPp7JAYqGtzPO0PpsBvpXj9ixIY0vbEjIJV5nPBLuiondBvfFPa'); +insert into customer values(1,1,7,.2211,'GC','BARBARANTI','yP3UO14nDcKVWT',50000.00,-10.00,10.00,1,1,'CsRfGWBSiHsM48m','nQcWfwSnQ8rx','s3BiVOmYlUVDULfUd5Sh','NJ',986511111,9852899873047153,'2021-05-03 18:17:11.558','OE','D2HYFitKPl71BaupGFL7dpmvCZBF9sVeKrCZtQCq3tjuHRPAiRUolinxKqGqygM9ifnwULISJR9U2l5CwpELoyFetm5HJnAo9MM7sdcpyZyrb60hk5lZhTcjvlqTCsA2jcn7b4Q6aI20tyxwZLwD8G5lWvRFR9VpKbQZyVNwlR1yUPn5iXTYJemslhS35jNfVympGSPtt42RZz3pl3Y7iLAg3naB4E9tX0rLCyix0pjNJ1pEJVZkE0lUdLB4BSv7qBTRgl6NytO5Pvvvvq9vY13lHH8uBcK5Snv8pcakmtWNqxFUMkVIkQmpQywWKWQbvzvN9v9MKCT6y6O2x7vRADDmLC5QvA4pMwmR5dshCVUoqLYAS1YxWo2kkGdGLYKooLjXjLCMt'); +insert into customer values(1,1,8,.4275,'GC','BARBARCALLY','AmIEo5hro',50000.00,-10.00,10.00,1,1,'T38BcTmUfrfwlVE','k7fJJVbI5PEH8dwv3zdd','MSAOPNhZbRlT2jMJG','BH',312011111,9652687324079719,'2021-05-03 18:17:11.558','OE','BEQM7mOJK9pDfNqnLF7ppP36SiV0widFPOT6DU3YY0VFnxP5veTUngwPtlxVgF7t2G8a8PTxHRw3GNb1RrYRiR13uBd49Mqzmtee37LUyCPGyyze5ZQtTvRdhun2Vucfwv1R4esQ6bPoAgnteYLyWzBXBod2ZNOruLNNqfm3r9OUOyDDkzz69wpEKxa6MXISoSe9OrfkBKiza76N09YowaM1s10IsIrqSDW6HhW91hszYemTjJRiUUSo72tW9mTTZiVrtGYkrDyYbBBKhqtdPasJ7I6SUkEtFyM77WFYSsPrVGG2Dd9AWvCN6eby8Cpvbwpo6egBqQq2ckZkTlbOPmde2r4XwAr5lmOOVo63ebUOJD7l5LQp4tNUYMn6UxLH5MaUizguaISlgjYqykKfx5sz5Fh5LPNmY5HiVGcc9JYEPpT'); +insert into customer values(1,1,9,.0230,'BC','BARBARATION','o7S4i3Fyzj7CE',50000.00,-10.00,10.00,1,1,'uew980V3LMjRWlaMkr','DbXnfpT5t9gh','L2QLwQzOZkX','EQ',741911111,1093277057883824,'2021-05-03 18:17:11.558','OE','dZrWWxARghTi7R3cF8CwnOUkZtmVVQLZvvZpPFOaZnqgPA9G2FLG2Vr7F6h7o2wlkOUiv67xxS1TiZ1HNhFBe8H0w1xNTXyG2Hj0pXJZ1ArSXufD0KTpJmcIQqRshwEIa8lHG34LA0mnexakbU0nkMgRSam8EZyES2o9GFcDmmSVtM3hmrC7jOuI2ggJo3uMd1QrViWh9FltJHiENTyRfV880oRBtcKevmG1S3CTedq4XPQQwBKK6Bm3uBiPfiby6dIeYNeMEPcoM7C0W5y4llCR0nWwFhpOFi3SrmWZ5JJhanfLgSQTjdLrfBZo9yjrPSFQjLF2cbTQAgWf'); +drop procedure if exists functions; +NOTICE: function functions() does not exist, skipping +-- max, min, avg, count, left, right, update, select, substring, coalesce, extract, epoch, concat, rtrim, abs, position, order by +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 integer, OUT out4 timestamp, OUT out5 timestamp, +OUT out6 decimal, OUT out7 decimal, OUT out8 character varying, OUT out9 character varying, OUT out10 integer, OUT out11 character varying, +OUT out12 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + + SELECT COUNT(*) INTO v_count1 from customer where c_credit like 'G%'; + out1 := v_count1; + + SELECT COUNT(*) INTO v_count2 FROM customer + where c_w_id = 1 + and c_d_id < 10 + and c_id <> 100 + and c_credit like 'G%' + GROUP BY c_id + ORDER BY c_id + LIMIT 1; + out2 := v_count2; + + select count( distinct c_id) into out3 from customer where c_d_id <10; + select max( c_since) into out4 from customer; + select min( c_since) into out5 from customer; + select avg( c_discount) into out6 from customer; + update customer set c_credit_lim = c_balance - c_ytd_payment where c_d_id <> 5; + select count(*) into out7 from customer where c_d_id BETWEEN 6 AND 9; + + select SUBSTRING( c_first, 1, 5 ) into out8 from customer where c_balance = (select max(c_balance) from customer) order by 1 limit 1; + select COALESCE (c_first, LEFT(c_data, 15)) into out9 from customer order by c_first desc limit 1; + select extract(epoch from c_since) - extract(epoch from c_since - 10) into out10 from customer limit 1; + select concat((select left(c_data,5) from customer order by c_first desc limit 1), (select right(c_data,5) from customer order by c_first asc limit 1)) into out11 from customer limit 1; + select count (rtrim (c_phone, '260944')) into v_count2 from customer; + select max(abs(POSITION('XXX' IN c_data))) into out12 from customer; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select functions(); + functions +----------------------------------------------------------------------------------------------------------------------------------------- + (7,1,9,"Mon May 03 18:17:11.558 2021","Mon May 03 18:17:11.558 2021",.23598888888888888889,0,AmIEo,yP3UO14nDcKVWT,864000,D2HYFYEPpT,0,) +(1 row) + +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +-------------------------------------------------------------------------- +-- sum, ceiling, char_length, length, cos, lower, upper, mod +drop procedure if exists functions; +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 integer, OUT out4 numeric, OUT out5 integer, +OUT out6 character varying, OUT out7 character varying, OUT out8 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + select sum(c_id) into out1 from customer; + SELECT CEILING(c_discount + 9.76) into out2 from customer order by c_discount desc limit 1; + SELECT char_length(c_data) into out3 from customer order by c_data desc limit 1; + SELECT length(c_data) into out4 from customer order by c_data desc limit 1; + select cos(c_balance) into out5 from customer limit 1; + select lower(c_first) into out6 from customer where c_id = 1 order by c_first limit 1; + select upper(c_first) into out7 from customer where c_id = 1 order by c_first limit 1; + select mod((select sum(c_id) from customer), 3) into out8 from customer limit 1; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select functions(); + functions +------------------------------------------- + (45,11,410,410,-1,k1qxjyux8,K1QXJYUX8,0,) +(1 row) + +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +------------------------------------------------------------------------- +-- REGEXP_MATCHES, sqrt, +drop procedure if exists functions; +CREATE OR REPLACE PROCEDURE functions(OUT out1 character varying, OUT out2 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + SELECT REGEXP_MATCHES(c_data, '([A-Za-z0-9_]+)','g') into out1 from customer order by c_data desc limit 1; + select sqrt((select sum(c_id) from customer)) into out2 from customer limit 1; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select functions(); + functions +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ({qJ4LHCniHIiv5dnReoE36YzJnndwpSTXp2VWCPcVQYJ2SZxJ6RfQAt6zs3wwyl3rEDeoo5UEAzZKndJaqaU5I27X1u0jNUUXGtnM24Q9i4L3rY9ysXzokgB0eReIBPHmcizS7qtULcIy8cyD3FrmsvVgABPeocvNSwltSxLkXm0G9yI2Z9lTmWQomiDWoYDGQtCKxxqwRFGz3KeKf8mh1HicQSPL57wP3cC5SK3JPjhRMlldWrUzthBa6orKIJhpHkz4UrJCvce71o9lWE0DezPuMQZvVs3vHey3E9D8my9ZhvnphCROC8qJ2pIQ7Jv0asc5kUCFPkOrLz9KOmYnsP80KFVy377AMjuFTnTtXCExxDPjCJaJf1exzgfcBGLMxDjcw1HX7f4beI10GyX2P2KOuE},7,) +(1 row) + +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +------------------------------------------------------------------------ +-- create, update, delete, loop, extract, day, month, year, left join, right join, inner join, IN +drop procedure if exists functions; +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 character varying, OUT out3 character varying, OUT out4 character varying, OUT out5 integer, OUT out6 integer, +OUT out7 integer, OUT out8 integer, OUT out9 integer, OUT out10 integer, OUT err_msg character varying) +AS +DECLARE + counter integer := 0; + v_count2 integer := 0; + vsum integer := 0; +BEGIN + create foreign table if not exists tmp (like customer); + insert into tmp select * from customer; + update tmp set c_balance = c_balance + c_credit_lim where (c_d_id = 2 or c_d_id = 3) and c_credit like 'G%'; + delete from tmp where (c_d_id = 2 or c_d_id = 3) and c_credit not like 'G%'; + + select count (*) into v_count2 from tmp where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%'; + + WHILE TRUE LOOP + counter := counter + 1; + IF counter = v_count2 THEN + update tmp set c_balance = c_balance + c_credit_lim where (c_w_id = 1 and c_d_id = counter and c_id = vsum); + delete from tmp where (c_d_id = 5 or c_id = 3) and c_credit like 'G%'; + EXIT; + END IF; + + vsum := vsum + counter; + END LOOP; + out1 = vsum; + + + SELECT EXTRACT(YEAR FROM c_since) into out2 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + SELECT EXTRACT(MONTH FROM c_since) into out3 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + SELECT EXTRACT(DAY FROM c_since) into out4 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + + select count(*) into out5 from customer left join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out6 from customer right join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out7 from customer inner join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out8 from customer left outer join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out9 from customer right outer join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + + select count(*) into out10 from tmp where c_w_id IN (1,2,3) and c_d_id IN (3,2,1) and c_id IN (4,5,6); + drop foreign table tmp; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select functions(); + functions +--------------------------- + (1,2021,5,3,9,9,0,9,9,3,) +(1 row) + +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +------------------------------------------------------------------------ +drop procedure if exists functions; +-- for loop, while loop, cast, delete using, +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 character varying, OUT out4 integer, OUT out5 integer, OUT err_msg character varying) +AS +DECLARE + counter integer := 0; + total_balance integer := 0; + balance integer := 0; + genre_rec character varying; + v_loop integer := 0; + v_out integer; +BEGIN + + create foreign table if not exists tmp (like customer); + create index on tmp(c_w_id, c_d_id, c_id); + insert into tmp select * from customer; + select count(*) into counter from customer; + out1 := counter / 100; + + FOR i IN 1..out1 LOOP + delete from tmp using customer where tmp.c_w_id = customer.c_w_id and tmp.c_d_id = customer.c_d_id and tmp.c_id = customer.c_id limit 1; + END LOOP; + select count (*) into out2 from tmp; + + WHILE TRUE LOOP + IF v_loop >= out1 THEN + EXIT; + END IF; + + IF ((v_loop % 2) = 1) THEN + update tmp set c_balance = c_balance + 1 where c_w_id % 2 = 1 and c_d_id % 2 = 1 and c_id = 1; + ELSE + insert into tmp select * from customer where c_w_id % 2 = 1 and c_d_id % 2 = 1 and c_id % 2 = 1 limit 1; + END IF; + + v_loop := v_loop + 1; + END LOOP; + + select count (*) into v_out from tmp; + + select CAST (v_out AS character varying) into out3; + out4 := out3::integer; + + update customer set c_delivery_cnt = c_delivery_cnt + c_payment_cnt + c_discount where c_balance != 0 and c_city like '%y' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13 and c_id in (131,295.665); + + select count (*) into out5 from customer where c_balance != 0 and c_city like '%y%' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13 and c_id in (select c_id from customer where c_city like '%y%' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13); + drop foreign table tmp; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select functions(); + functions +-------------- + (0,9,9,9,0,) +(1 row) + +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; +drop foreign table customer; +drop procedure if exists functions; diff --git a/src/test/regress/expected/mot/single_sp_queries_rtd2.out b/src/test/regress/expected/mot/single_sp_queries_rtd2.out new file mode 100644 index 000000000..602f0db9f --- /dev/null +++ b/src/test/regress/expected/mot/single_sp_queries_rtd2.out @@ -0,0 +1,160 @@ +------------------------------------------------------------------------------------ +create foreign table table1 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020)); +create foreign table table2 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020)); +CREATE OR REPLACE PROCEDURE random_text_simple(length INTEGER, out random_text_simple TEXT) + AS + DECLARE + possible_chars TEXT := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + output TEXT := ''; + i INT4; + pos INT4; + BEGIN + + FOR i IN 1..length LOOP + pos := random_range(1, length(possible_chars)); + output := output || substr(possible_chars, pos, 1); + END LOOP; + + random_text_simple := output; + END; +/ +CREATE OR REPLACE FUNCTION random_range(INTEGER, INTEGER) + RETURNS INTEGER + LANGUAGE SQL + AS $$ + SELECT ($1 + FLOOR(($2 - $1 + 1) * random() ))::INTEGER; +$$; + +CREATE OR REPLACE PROCEDURE insert_into_table1(max integer, OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table1 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable1 from table1; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +CREATE OR REPLACE PROCEDURE insert_into_table2(max integer, OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table2 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable1 from table2; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +select insert_into_table1(1000); + insert_into_table1 +-------------------- + (1000,) +(1 row) + +select insert_into_table2(1000); + insert_into_table2 +-------------------- + (1000,) +(1 row) + +CREATE OR REPLACE PROCEDURE insert_into(max integer, OUT outable1 integer, OUT outable2 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; +BEGIN + insert into table1 values (generate_series(1,v_max)); + insert into table2 select * from table1 where c1 is null; + select count(*) into outable1 from table1; + select count(*) into outable2 from table2; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ +select insert_into(1000); + insert_into +-------------- + (2000,2000,) +(1 row) + +CREATE OR REPLACE PROCEDURE delete_from(OUT outable1 integer, OUT outable2 integer, OUT err_msg character varying) +AS +DECLARE + t_record record; +BEGIN + create index on table1(x); + create index on table2(x); + truncate table1; + truncate table2; + select insert_into_table1(1000) into t_record; + select insert_into_table2(1000) into t_record; + select insert_into(1000) into t_record; + delete from table1 using table2 where table1.x = table2.x and table1.c1 = table2.c2; + delete from table2 using table1 where table1.x = table2.x and table1.c1 != table2.c2; + + select count (*) into outable1 from table1; + select count (*) into outable2 from table2; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ +select delete_from(); + delete_from +-------------- + (2000,1000,) +(1 row) + +CREATE OR REPLACE PROCEDURE compare(OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + t_record record; +BEGIN + select count (*) into outable1 from table1,table2 where table1.x = table2.x and table1.c1 like '%%%' and table2.c1 like '%%%'; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ +select compare(); + compare +--------- + (0,) +(1 row) + +drop foreign table table1; +drop foreign table table2; diff --git a/src/test/regress/expected/mot/single_sp_queries_rtd3.out b/src/test/regress/expected/mot/single_sp_queries_rtd3.out new file mode 100644 index 000000000..7e5a06142 --- /dev/null +++ b/src/test/regress/expected/mot/single_sp_queries_rtd3.out @@ -0,0 +1,145 @@ +SET check_function_bodies = false; +drop foreign table if exists table111; +NOTICE: foreign table "table111" does not exist, skipping +create foreign table table111 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020), primary key(x)); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "table111_pkey" for foreign table "table111" +CREATE OR REPLACE PROCEDURE random_text_simple(length INTEGER, out random_text_simple TEXT) + AS + DECLARE + possible_chars TEXT := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + output TEXT := ''; + i INT4; + pos INT4; + BEGIN + + FOR i IN 1..length LOOP + pos := random_range(1, length(possible_chars)); + output := output || substr(possible_chars, pos, 1); + END LOOP; + + random_text_simple := output; + END; +/ +CREATE OR REPLACE FUNCTION random_range(INTEGER, INTEGER) + RETURNS INTEGER + LANGUAGE SQL + AS $$ + SELECT ($1 + FLOOR(($2 - $1 + 1) * random() ))::INTEGER; +$$; + +CREATE OR REPLACE PROCEDURE build(max integer, OUT outable111 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table111 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable111 from table111; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ +CREATE OR REPLACE PROCEDURE delfunc(id numeric) AS + BEGIN + DELETE FROM table111 + WHERE x = id; + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ +CREATE OR REPLACE PROCEDURE updatefunc(id numeric) AS + DECLARE + v_c1 character varying; + v_c2 character varying; + v_c3 character varying; + v_c4 character varying; + v_c5 character varying; + BEGIN + select random_text_simple(5) into v_c1; + select random_text_simple(5) into v_c2; + select random_text_simple(5) into v_c3; + select random_text_simple(5) into v_c4; + select random_text_simple(5) into v_c5; + UPDATE table111 set c1 = v_c1, c2 = v_c2, c3 = v_c3, c4 = v_c4, c5 = v_c5 + WHERE x = id; + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ +CREATE OR REPLACE PROCEDURE insertfunc(id numeric) AS + DECLARE + t_record record; + BEGIN + INSERT INTO table111 VALUES (id); + select updatefunc(id) into t_record; + + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ +CREATE OR REPLACE PROCEDURE bench(length INTEGER, OUT outable111 character varying, OUT err_msg character varying) +AS + DECLARE + i integer; + var_case integer; + t_record record; + count_all integer; + distinct_count integer; + res character varying; + BEGIN + + FOR i IN 1..length LOOP + SELECT floor(random()*(3-1+1))+1 into var_case; + + CASE var_case + WHEN 1 THEN select insertfunc(i+100) into t_record; + WHEN 2 THEN select delfunc(i) into t_record; + WHEN 3 THEN select updatefunc(i) into t_record; + END CASE; + END LOOP; + + select count (*) from table111 into count_all; + select count (distinct(x)) from table111 into distinct_count; + + IF count_all = distinct_count + THEN res := 'SUCCESS'; + ELSE res := 'FAIL'; + END IF; + outable111 := res; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ +select build(100); + build +-------- + (100,) +(1 row) + +select bench(100); + bench +------------ + (SUCCESS,) +(1 row) + +drop foreign table table111; diff --git a/src/test/regress/expected/mot/single_sp_queries_rtd5.out b/src/test/regress/expected/mot/single_sp_queries_rtd5.out new file mode 100644 index 000000000..59f640e2c --- /dev/null +++ b/src/test/regress/expected/mot/single_sp_queries_rtd5.out @@ -0,0 +1,170 @@ +create foreign table t_auto_call( + SM_USER_ID int not null primary key, + ROSTER_ID varchar (48), + DD_APPDATE date not null + ); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "t_auto_call_pkey" for foreign table "t_auto_call" +create foreign table t_prmi_transaction( + SM_USER_ID int not null, + SD_KEY int not null, + SEQ_NO varchar(32), + SD_PT_SRV_ENTRY_NDE_L2 varchar(2), + SD_PT_SRV_COND_CDE varchar(4), + SD_TERM_CNTRY varchar(4), + SD_ACO_INST_ID_NUM_R4 int, + DD_APPDATE date not null + ); + +CREATE OR REPLACE PROCEDURE select_count(ev_ddApdate date, en_smUserId int, ev_sdKey int, ev_sdTermCntry character varying, ev_sdPtSrvCondCde varchar, +ev_sdAcqInstIdNumR4 int, result_type varchar, flag char, send_user varchar, OUT rv_autocall_wk_24h_abroadcntry INTEGER, OUT msg character varying) + AS +BEGIN + select count (1) into rv_autocall_wk_24h_abroadcntry + from T_AUTO_CALL t_auto_call, T_PRMI_TRANSACTION t_prmi_transaction + where t_auto_call.SM_USER_ID = en_smUserId + and t_prmi_transaction.SM_USER_ID = en_smUserId + and t_prmi_transaction.SD_KEY = ev_sdKey + and result_type = 'OK' + and flag = 'I' + and (send_user = 'PRMI' or right( roster_id, 9) = 'MANU_CALL') + and left(t_auto_call.ROSTER_ID, 32) = t_prmi_transaction.SEQ_NO + and t_prmi_transaction.SD_PT_SRV_ENTRY_NDE_L2 not in ('01','02','81','10','04','94') + and t_prmi_transaction.SD_PT_SRV_COND_CDE in ('V001','M001','A001','J001','N012','A003') + and ( + t_prmi_transaction.SD_TERM_CNTRY = ev_sdTermCntry + and ev_sdPtSrvCondCde != 'NO12' + and t_prmi_transaction.SD_PT_SRV_COND_CDE = 'N012' + or + t_prmi_transaction.SD_ACO_INST_ID_NUM_R4 = ev_sdAcqInstIdNumR4 + and t_prmi_transaction.SD_PT_SRV_COND_CDE = 'N012' + and ev_sdPtSrvCondCde = 'N012' + ) + and extract(second from ev_ddApdate) <= extract(second from t_prmi_transaction.DD_APPDATE) + 3600*24 + --and since_epoch(second, cast(ev_ddApdate as timestamp)) <= since_epoch(second, cast(t_prmi_transaction.DD_APPDATE as timestamp)) + 3600*24 + and extract(second from ev_ddApdate) <= extract(second from t_auto_call.DD_APPDATE) + 3600*24; + --and since_epoch(second, cast(ev_ddApdate as timestamp)) <= since_epoch(second, cast(t_auto_call.DD_APPDATE as timestamp)) + 3600*24; + + msg := 'OK'; +EXCEPTION + WHEN OTHERS THEN + msg := SQLERRM; +END; +/ +create index on t_auto_call(DD_APPDATE); +create index on t_prmi_transaction(SM_USER_ID); +create index on t_prmi_transaction(SD_KEY); +create index on t_prmi_transaction(DD_APPDATE); +insert into t_auto_call values (1, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); +insert into t_auto_call values (2, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); +insert into t_auto_call values (3, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '02', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 2, 'qwertyuiopasdfghjklzxcvbnmqwerty', '81', 'V001', 'XXZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (2, 1, 'qwertyuiopasdfghjklzxcvbnmqwery', '04', 'J001', 'XYY', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 3, 'qwertyuiopasdfghjklzxcvbnmqwerty', '94', 'A003', 'XZZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '01', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '07', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '00', 'N011', 'XYZ', 1, current_date - '1 day'::INTERVAL); +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,2,'XYY','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,2,1,'XZZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,3,1,'XXZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XZ','N012',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XZ','N017',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'i', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N012',2,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N012',10,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XYZ','N0123',10,'OK', 'I', 'PRMI'); + select_count +-------------- + (1,OK) +(1 row) + +select select_count(current_date,1,1,'XYzZ','N0123',10,'OK', 'I', 'PRMI'); + select_count +-------------- + (0,OK) +(1 row) + +select select_count(current_date,1,1,'XYzZ','N0123',10,'OK', 'I', 'PMI'); + select_count +-------------- + (0,OK) +(1 row) + +drop foreign table t_auto_call; +drop foreign table t_prmi_transaction; diff --git a/src/test/regress/expected/mot/single_sp_queries_rtd6.out b/src/test/regress/expected/mot/single_sp_queries_rtd6.out new file mode 100644 index 000000000..400504a8c --- /dev/null +++ b/src/test/regress/expected/mot/single_sp_queries_rtd6.out @@ -0,0 +1,128 @@ +create foreign table t_login( + pid integer, + PHY_ID integer, + BSS_ID varchar(16), + DD_APDATE timestamp, + seq_No integer, + primary key (pid, seq_No) +); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "t_login_pkey" for foreign table "t_login" +create foreign table t_name_list( + obj_no varchar(16), + obj_quality char, + expire_date timestamp +); +CREATE OR REPLACE PROCEDURE login(v_cnt integer, ev_pid integer, ev_phyId integer, ev_ddApdate timestamp with time zone, out rv_loginWiFiwhiteList TEXT) + AS + DECLARE + v_loop integer := 0; + v_timestamp timestamp; + v_seqNo integer; + v_bssid varchar(16); + v_flag integer; + BEGIN + rv_loginWiFiwhiteList := ''; + /*if connected times more than 0*/ + if v_cnt > 0 then + rv_loginWiFiwhiteList := 'Y'; + --RAISE EXCEPTION 'if v_cnt > 0, v_cnt = (%), rv_loginWiFiwhiteList := Y', v_cnt; + else + /*check if connected on last week*/ + select count(1) into v_cnt + from t_login + where pid = ev_pid + and PHY_ID = ev_phyId + and BSS_ID like 'd4:68:ba%' + and extract(second from ev_ddApdate) - extract(second from DD_APDATE) <= 604800; + --and since_epoch(second, ev_ddApdate) - since_epoch(second, DD_APDATE) <= 604800; + /*if not connected on last week rv_loginWiFiwhiteList = N*/ + if (v_cnt = 0) then + rv_loginWiFiwhiteList := 'N'; + --RAISE EXCEPTION 'if v_cnt = 0, v_cnt = (%), rv_loginWiFiwhiteList := N', v_cnt; + else + --RAISE EXCEPTION ' else, v_cnt = (%)', v_cnt; + v_loop := 0; + v_timestamp := ev_ddApdate; + + while (v_loop < v_cnt + and (rv_loginWiFiwhiteList != 'Y' or rv_loginWiFiwhiteList is null or rv_loginWiFiwhiteList = '')) loop + + select seq_No into v_seqNo + from t_login + where pid = ev_pid + and PHY_ID = ev_phyId + and BSS_ID like 'd4:68:ba%' + and extract(second from ev_ddApdate) - extract(second from DD_APDATE) <= 604800 + --and since_epoch(second, ev_ddApdate) - since_epoch(second, DD_APDATE) <= 604800 + and DD_APDATE < v_timeStamp + order by DD_APDATE desc limit 1; + + select DD_APDATE into v_timeStamp + from t_login + where pid = ev_pid + and seq_No = v_seqNo order by DD_APDATE limit 1; + + select BSS_ID into v_bssid + from t_login + where pid = ev_pid + and seq_No = v_seqNo ; + + select count(1) into v_flag + from t_name_list + where obj_no = v_bssid + and obj_quality = 'w' + and expire_date > ev_ddApdate; + + if (v_flag > 0) then + rv_loginWiFiwhiteList := 'Y'; + --RAISE EXCEPTION ' if(v_flag > 0), v_flag = (%)', v_flag; + end if; + + v_loop := v_loop + 1; + end loop; + + if (v_loop = v_cnt + and (rv_loginWiFiwhiteList != 'Y' or rv_loginWiFiwhiteList is null or rv_loginWiFiwhiteList = '')) then + rv_loginWiFiwhiteList := 'N'; + end if; + end if; + end if; +END; +/ +insert into t_login values (1,1,'d4:68:ba%', current_timestamp, 1); +select login(1,1,1,current_timestamp); + login +------- + Y +(1 row) + +insert into t_name_list values ('d4:68:ba%','y', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); + login +------- + N +(1 row) + +insert into t_login values (1,1,'d4:68:ba:dd%', current_timestamp, 2); +select login(0,1,1,current_timestamp); + login +------- + N +(1 row) + +insert into t_name_list values ('d4:68:ba:da','w', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); + login +------- + N +(1 row) + +insert into t_name_list values ('d4:68:ba%','w', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); + login +------- + Y +(1 row) + +drop foreign table t_login; +drop foreign table t_name_list; diff --git a/src/test/regress/expected/mot/single_supported_unsupported_types.out b/src/test/regress/expected/mot/single_supported_unsupported_types.out index d873aa651..0757d8065 100644 --- a/src/test/regress/expected/mot/single_supported_unsupported_types.out +++ b/src/test/regress/expected/mot/single_supported_unsupported_types.out @@ -37,6 +37,9 @@ DETAIL: Column type inet is not supported yet create foreign table test14 (m macaddr); ERROR: Column definition of m is not supported DETAIL: Column type macaddr is not supported yet +create foreign table test15 (t text); +ERROR: Column definition of t is not supported +DETAIL: Column size -1 exceeds max size 2052 drop foreign table test0; drop foreign table test1; drop foreign table test2; diff --git a/src/test/regress/expected/mot/single_update.out b/src/test/regress/expected/mot/single_update.out index 89af0f31a..8e5ccb36c 100644 --- a/src/test/regress/expected/mot/single_update.out +++ b/src/test/regress/expected/mot/single_update.out @@ -18,7 +18,7 @@ SELECT * FROM products ORDER BY id; -- The next command should fail as the primary key is immutable. UPDATE products SET id=42 where price=120.0; -ERROR: Update of indexed column is not supported for memory table +ERROR: Update of primary key column is not supported for memory table SELECT * FROM products ORDER BY id; id | price ----+-------- diff --git a/src/test/regress/expected/mot/single_update_secondary_index_column.out b/src/test/regress/expected/mot/single_update_secondary_index_column.out new file mode 100644 index 000000000..b8dc54e1f --- /dev/null +++ b/src/test/regress/expected/mot/single_update_secondary_index_column.out @@ -0,0 +1,559 @@ +--T1 +drop foreign table test_update_column; +ERROR: foreign table "test_update_column" does not exist +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +--T2 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +commit; +--T3 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +begin; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +commit; +--T4 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +--T5 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +update test_update_column set z=4 where x=1; +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (z)=(4) already exists. +select * from test_update_column; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +--T6 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +insert into test_update_column values (3,3,4,100); +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (z)=(4) already exists. +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +--T7 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=5 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 +(1 row) + +insert into test_update_column values (3,3,4,100); +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +--T8 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=5 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 +(1 row) + +insert into test_update_column values (3,3,4,100); +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +update test_update_column set y=100 where y=3; +commit; +select * from test_update_column; + x | y | z | data +---+-----+---+------ + 1 | 100 | 5 | 100 + 3 | 100 | 4 | 100 +(2 rows) + +--T9 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +update test_update_column set z=5; +ERROR: duplicate key value violates unique constraint "idx2" +DETAIL: Key (z)=(5) already exists. +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +--T10 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +alter foreign table test_update_column drop column y; +select * from test_update_column; + x | z | data +---+---+------ + 1 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | z | data +---+---+------ + 1 | 4 | 100 +(1 row) + +--T11 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +alter foreign table test_update_column drop column z; +select * from test_update_column; + x | y | data +---+---+------ + 1 | 3 | 100 +(1 row) + +--T12 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +alter foreign table test_update_column drop column z; +commit; +select * from test_update_column; + x | y | data +---+---+------ + 1 | 3 | 100 +(1 row) + +--T13 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +alter foreign table test_update_column drop column z; +update test_update_column set z=4 where x=1; +ERROR: column "z" of relation "test_update_column" does not exist +LINE 1: update test_update_column set z=4 where x=1; + ^ +select * from test_update_column; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +--T14 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=4 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 4 | 100 +(1 row) + +alter foreign table test_update_column drop column z; +commit; +select * from test_update_column; + x | y | data +---+---+------ + 1 | 3 | 100 +(1 row) + +--T15 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 3 | 100 +(1 row) + +update test_update_column set y=3 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 3 | 100 +(1 row) + +update test_update_column set z=5 where x=1; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 +(1 row) + +insert into test_update_column values (3,3,4,100); +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 3 | 5 | 100 + 3 | 3 | 4 | 100 +(2 rows) + +update test_update_column set y=100 where y=3; +select * from test_update_column; + x | y | z | data +---+-----+---+------ + 1 | 100 | 5 | 100 + 3 | 100 | 4 | 100 +(2 rows) + +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + x | z | data +---+---+------ + 1 | 5 | 100 + 3 | 4 | 100 +(2 rows) + +--T16 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | 100 + 3 | 3 | 6 | 100 +(2 rows) + +--T17 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + x | z | data +---+---+------ + 1 | 4 | 100 + 3 | 6 | 100 +(2 rows) + +--T18 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +commit; +select * from test_update_column; + x | y | z | data +---+---+---+------ + 1 | 2 | 4 | 100 + 3 | 3 | 6 | 100 +(2 rows) + +--T19 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +NOTICE: CREATE FOREIGN TABLE / PRIMARY KEY will create constraint "test_update_column_pkey" for foreign table "test_update_column" +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + x | z | data +---+---+------ + 1 | 4 | 100 + 3 | 6 | 100 +(2 rows) + +drop foreign table test_update_column; diff --git a/src/test/regress/expected/single_node_opr_sanity.out b/src/test/regress/expected/single_node_opr_sanity.out index d1e767906..e1280a868 100755 --- a/src/test/regress/expected/single_node_opr_sanity.out +++ b/src/test/regress/expected/single_node_opr_sanity.out @@ -1454,6 +1454,8 @@ WHERE d.classoid IS NULL AND p1.oid <= 9999 order by 1; 6202 | mot_local_memory_detail 6203 | pg_start_backup 6204 | pg_stop_backup + 6205 | mot_jit_detail + 6206 | mot_jit_profile 6224 | gs_get_next_xid_csn 6321 | pg_stat_file_recursive 6322 | gs_decrypt diff --git a/src/test/regress/parallel_schedule20 b/src/test/regress/parallel_schedule20 index 3deb9565d..bdf1d9c08 100644 --- a/src/test/regress/parallel_schedule20 +++ b/src/test/regress/parallel_schedule20 @@ -30,3 +30,12 @@ test: mot/single_update test: mot/single_supported_unsupported_types test: mot/single_relation_size test: mot/single_join_cross_engine_check +test: mot/single_sp_queries_rtd +test: mot/single_sp_queries_rtd2 +test: mot/single_sp_queries_rtd3 +test: mot/single_sp_queries_rtd5 +test: mot/single_sp_queries_rtd6 +test: mot/single_new_indexes +test: mot/single_new_indexes2 +test: mot/single_new_indexes3 +test: mot/single_update_secondary_index_column diff --git a/src/test/regress/single_check.sh b/src/test/regress/single_check.sh index d78282842..d281114a6 100755 --- a/src/test/regress/single_check.sh +++ b/src/test/regress/single_check.sh @@ -251,7 +251,8 @@ function real_hacheck() sh ./run_ha_multi_single.sh 1 ${part} sh ./run_ha_multi_cascade.sh ;; hacheck_multi_single_mot) - sh ./run_ha_multi_single_mot.sh 1 ${part} ;; + sh ./run_ha_multi_single_mot.sh 1 ${part} + sh ./run_ha_multi_cascade_mot.sh ;; hacheck_single_paxos) sh ./run_paxos_single.sh ;; hacheck_ss_all) diff --git a/src/test/regress/sql/mot/single_new_indexes.sql b/src/test/regress/sql/mot/single_new_indexes.sql new file mode 100644 index 000000000..8b101bca2 --- /dev/null +++ b/src/test/regress/sql/mot/single_new_indexes.sql @@ -0,0 +1,172 @@ + +-- BUG 92 + +create foreign table test_new_index (x float4 not null, y float8 not null, z varchar(1) not null); + +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); + +insert into test_new_index values (1,1,'1'); +select * from test_new_index where z = 49; + +-- CRASH +drop foreign table test_new_index; + +-- BUG 91 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); + +insert into test_new_index (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); + +delete from test_new_index where y > x; + +drop foreign table test_new_index; + +-- BUG 90 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); + +insert into test_new_index (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); + +select * from test_new_index where x = 9; + +drop foreign table test_new_index; + +-- BUG 89 +create foreign table test_new_index (i integer not null, i2 int2 not null, i4 int4 not null, i8 int8 not null, n numeric not null, f4 float4 not null, f8 float8 not null, s serial not null, c char not null, v varchar(10) not null, vchar varchar(1020) not null, d date not null, t time not null, ts timestamp not null, intrvl interval not null, b boolean not null); + +create index idx1 on test_new_index (i); +create index idx2 on test_new_index (i2); +create index idx3 on test_new_index (i4); +create index idx4 on test_new_index (i8); +create index idx5 on test_new_index (i,i2,i4); +create index idx6 on test_new_index (f4,f8); +create index idx7 on test_new_index (c,v); +create index idx8 on test_new_index (d,t,ts); +create index idx9 on test_new_index (intrvl); +create index idx10 on test_new_index (b); + +insert into test_new_index (i, i2, i4) values (generate_series(1,100), generate_series(1,100), generate_series(1,100)); +select * from test_new_index order by i limit 10; + +drop foreign table test_new_index; + +-- BUG 87 +create foreign table test_new_index (f1 float4 not null, f2 float8 not null, primary key (f1, f2)) ; + +create index idx1 on test_new_index (f1); +create index idx2 on test_new_index (f2); + +insert into test_new_index values (0.0, 0.1); +insert into test_new_index values (0.0, -0.1); +insert into test_new_index values (-0.10, 0.1); +insert into test_new_index values (0.101, 0.1); +insert into test_new_index values (1.0, 0.1); +insert into test_new_index values (-1.10000001, 0.1); + +select * from test_new_index order by f1; +select * from test_new_index where f1 > -10.2 order by f1, f2; + +drop foreign table test_new_index; + +-- BUG 86 +create foreign table test_new_index (v1 varchar(4) not null, v2 varchar(4) not null, primary key (v1, v2)); +create index idx on test_new_index (v1); +create index idx2 on test_new_index (v2); + +insert into test_new_index values ('aaaa','aaaa'); +insert into test_new_index values ('aaaa','bbbb'); +insert into test_new_index values ('bbbb','bbbb'); +insert into test_new_index values ('bbbb','aaaa'); + +select * from test_new_index order by 1,2; + +select * from test_new_index where v1 = 'aaa'; +select * from test_new_index where v1 = 'bb'; + +drop foreign table test_new_index; + +-- BUG 85 +create foreign table test_new_index (x int not null, y int not null, z int not null, primary key(x,y,z)); + +create unique index udx1 on test_new_index (x,y,z); +create index udx1 on test_new_index (x,y,z); +create index idx1 on test_new_index (x); +create index idx2 on test_new_index (y); +create index idx3 on test_new_index (z); + +insert into test_new_index values (1,2,3); +insert into test_new_index values (2,3,4); +insert into test_new_index values (3,4,5); +insert into test_new_index values (4,5,6); +insert into test_new_index values (5,6,7); +insert into test_new_index values (6,7,8); + +select * from test_new_index where x > 4 and x = 5 order by x,y,z; + +drop foreign table test_new_index; + +-- BUG 84 +CREATE FOREIGN TABLE func_index_heap (f1 varchar(100) not null, f2 varchar(100) not null) ; +CREATE UNIQUE INDEX func_index_index on func_index_heap ((f1 || f2) text_ops); + +drop foreign table func_index_heap; +drop index func_index_index; + +-- BUG 83 +CREATE FOREIGN TABLE func_index_heap (f1 varchar(100) not null, f2 varchar(100) not null); +CREATE UNIQUE INDEX func_index_index on func_index_heap (textcat(f1,f2)); + +drop foreign table func_index_heap; +drop index func_index_index; + +-- BUG 82 +create foreign table test_new_index (i int not null, i2 int2 not null, i4 int4 not null, i8 int8 not null, primary key (i)); +create index idx1 on test_new_index (i, i2, i4, i8); +insert into test_new_index values (generate_series(1,100), generate_series(1,100), generate_series(1,100), generate_series(1,100)); + +select * from test_new_index where i = i2 and i2 = i4 and i4 = i8 and i8 = i order by i,i2,i4,i8 limit 10; + +drop foreign table test_new_index; + +-- BUG 81 +create foreign table test_new_index (v1 varchar(1) not null, v2 varchar(1) not null, primary key (v1, v2)); +create index idx on test_new_index (v1); +create index idx2 on test_new_index (v2); + +insert into test_new_index values ('a','a'); +insert into test_new_index values ('a','b'); +insert into test_new_index values ('b','b'); +insert into test_new_index values ('b','a'); + +select * from test_new_index order by 1,2; + +select * from test_new_index where v1 = 'a' order by v1,v2; +select * from test_new_index where v1 = 'ab'; +select * from test_new_index where v1 = 'abc'; +select * from test_new_index where v1 = 'abcd'; + +drop foreign table test_new_index; + +--BUG in MAX_KEY_LEN +create foreign table test_new_index (i varchar(8) not null, j varchar(244) not null); +create index tx on test_new_index (j); +create index tx2 on test_new_index (i); +create index tx3 on test_new_index (i,j); +create index tx4 on test_new_index (j,i); +create index tx7 on test_new_index (j); +create index tx8 on test_new_index (i); + +drop foreign table test_new_index;-- + + + + + + + diff --git a/src/test/regress/sql/mot/single_new_indexes2.sql b/src/test/regress/sql/mot/single_new_indexes2.sql new file mode 100644 index 000000000..b0987c227 --- /dev/null +++ b/src/test/regress/sql/mot/single_new_indexes2.sql @@ -0,0 +1,174 @@ + +-- BUG 94 +create foreign table test_new2 (x int primary key, y int not null, z int not null); +-- create duplicates +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); + +create index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (x); + +select * from test_new2 where x > 0 order by x; + +drop foreign table test_new2; + +-- BUG 95 +create foreign table test_new2 (x int not null, y int not null, z int not null); + +create index idx1 on test_new2 (x); +create index idx2 on test_new2 (y); +create index idx3 on test_new2 (z); +create unique index uidx on test_new2 (z,x); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); +truncate test_new2; +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); + +drop foreign table test_new2; + +-- BUG 97 +create foreign table test_new2 (x int not null, y int not null, z int not null); + +create index idx1 on test_new2 (x); +create index idx2 on test_new2 (y); +create index idx3 on test_new2 (z); + +insert into test_new2 values (1,1,1); +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; +insert into test_new2 select * from test_new2; + +drop foreign table test_new2; + +-- BUG 98 +create foreign table test_new2 (x int, y int, z int, primary key(x,y,z)); +insert into test_new2 values (1,2,3); +insert into test_new2 values (2,3,4); +insert into test_new2 values (3,4,5); +insert into test_new2 values (4,5,6); +insert into test_new2 values (5,6,7); +insert into test_new2 values (6,7,8); +select * from test_new2 where x = y = z; + +drop foreign table test_new2; + +-- TEST 1 + +create foreign table test_new2 (x varchar(16), y varchar(16) not null, z varchar(16), primary key(x,z)); +create index idx1 on test_new2 (y); + +insert into test_new2 values ('aa', 'aa', 'zz'); +insert into test_new2 values ('zz', 'aa', 'aa'); +insert into test_new2 values ('xxx', 'aa', 'tttt'); +insert into test_new2 values ('ii', 'aa', 'eee'); +insert into test_new2 values ('eeee', 'aa', 'iii'); + +drop foreign table test_new2; + +-- TEST 2 +create foreign table test_new2 (x int not null, y int, z int); +insert into test_new2 (y,x,z) values (generate_series(10,20), generate_series(10,20), generate_series(10,20)); + +create index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (x); + +select * from test_new2 where x > 0 order by x; + +drop foreign table test_new2; +-- + + +-- TEST 3 +create foreign table test_new2 (x int not null, y int not null, z int not null, primary key(x,y,z)); +create unique index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (y); +create unique index idx3 on test_new2 (z); + +insert into test_new2 values (1,2,3); +insert into test_new2 values (2,3,4); +insert into test_new2 values (3,4,5); +insert into test_new2 values (4,5,6); +insert into test_new2 values (5,6,7); +insert into test_new2 values (6,7,8); + +select * from test_new2 where x < 0 order by z desc; +select * from test_new2 where x <> 0 order by z desc; +insert into test_new2 values (-4,-5,-6); +select * from test_new2 where x < 0 order by z desc; +select * from test_new2 where x <> 0 order by z desc; + +insert into test_new2 values (0,-5,-6); +insert into test_new2 values (0,100,100); +select * from test_new2 where x <> 0 order by z ; +select * from test_new2 where x >= 0 and y <> 2 order by z ; + +drop foreign table test_new2; + +-- TEST 4 +create foreign table test_new21 (x int, y int, z int, primary key(x,y,z)); +create foreign table test_new22 (x int, y int, z int, primary key(x,y,z)); + +insert into test_new21 (y,x,z) values (generate_series(10,100), generate_series(10,100), generate_series(10,100)); +insert into test_new22 select * from test_new21; +truncate test_new21; +insert into test_new21 select * from test_new22; +truncate test_new22; + +drop foreign table test_new21; +drop foreign table test_new22; + +-- TEST 5 + +create foreign table test_new2 (x int not null, y int not null, z int not null) ; + +create unique index idx1 on test_new2 (x); +create unique index idx2 on test_new2 (y); +create unique index idx3 on test_new2 (z); + +insert into test_new2 values (1,1,1); +insert into test_new2 (x,y,z) values (2,2,2); + +insert into test_new2 (x) values (generate_series(10,20)); +select * from test_new2; + +drop foreign table test_new2; + +-- BUG 153 + +CREATE FOREIGN TABLE MATERIAL_BATCH_1_002_1( +C_CHAR_1 CHAR(1) not null, +C_CHAR_2 CHAR(10) not null, +C_CHAR_3 CHAR(100) not null, +C_VARCHAR_1 VARCHAR(1) not null, +C_VARCHAR_2 VARCHAR(10) not null, +C_VARCHAR_3 VARCHAR(64) not null, +C_INT BIGINT not null, +C_BIGINT BIGINT not null, +C_SMALLINT BIGINT not null, +C_FLOAT FLOAT not null, +C_NUMERIC numeric(20,5) not null, +C_DP double precision not null, +C_DATE DATE not null, +C_TS_WITHOUT TIMESTAMP not null, +C_TS_WITH TIMESTAMP not null); + +CREATE INDEX MATERIAL_INDEX_002_1 ON MATERIAL_BATCH_1_002_1(C_CHAR_1,C_CHAR_2,C_CHAR_3,C_VARCHAR_1,C_VARCHAR_2,C_VARCHAR_3,C_INT,C_BIGINT,C_TS_WITH); +CREATE INDEX MATERIAL_INDEX_002_2 ON MATERIAL_BATCH_1_002_1(C_CHAR_1,C_CHAR_2,C_CHAR_3,C_VARCHAR_1,C_VARCHAR_2,C_VARCHAR_3,C_INT,C_BIGINT,C_TS_WITH,C_SMALLINT); + +DROP FOREIGN TABLE MATERIAL_BATCH_1_002_1; + +-- + + + + + + + + + + + + diff --git a/src/test/regress/sql/mot/single_new_indexes3.sql b/src/test/regress/sql/mot/single_new_indexes3.sql new file mode 100644 index 000000000..6178d13f4 --- /dev/null +++ b/src/test/regress/sql/mot/single_new_indexes3.sql @@ -0,0 +1,424 @@ +--T0 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +begin; +insert into test_new_gc (x,y,z,data) values (1,2,3,0); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +update test_new_gc set data=100 where z=3; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; +begin; +update test_new_gc set data=200 where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; + + +--T1 + +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +begin; +insert into test_new_gc (x,y,z,data) values (1,2,3,0); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +update test_new_gc set data=100 where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; +begin; +update test_new_gc set data=200 where y=2; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; + + +--T2 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +delete from test_new_gc where z=3; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; + +--T3 +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +delete from test_new_gc where z=3; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +commit; + +--T4 Duplicates +delete from test_new_gc; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (1,2,3); + +--T5 Blocked Duplicates +delete from test_new_gc; +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (1,2,3); +commit; + +--T5.1 unique index duplicates! +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (2,3,3); + +--T5.2 unique index duplicates! +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +insert into test_new_gc (x,y,z) values (2,3,3); +commit; + +--T6 Insert-Delete-Insert All in A block +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; + +--T6.1 Insert-Delete-Insert All in A block - unique index +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; + + +--T7 Insert-Delete-Insert All in A block II +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,88); +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=88; + +--T7.1 Insert-Delete-Insert All in A block II - unique +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); +begin; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,3); +delete from test_new_gc where x=1; +insert into test_new_gc (x,y,z) values (1,2,88); +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=88; + +--New Delete Design + +--T8 Insert-Begin-Delete-Insert-Rollback +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +rollback; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; + +--T8.1 Insert-Begin-Delete-Insert-Rollback - unique +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=4; +rollback; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; + +--T9 Insert-Begin-Delete-Insert-Rollback +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +delete from test_new_gc where y=2; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=4; +insert into test_new_gc (x,y,z) values (1,2,5); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +select * from test_new_gc where z=5; +delete from test_new_gc where z=5; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=5; +commit; +--T10 +drop foreign table test_new; +create foreign table test_new (i int primary key, x int); +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +select * from test_new where i = 3; +commit; +--T11 +drop foreign table test_new; +create foreign table test_new (i int , x int); +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where x % 3 = 0; +insert into test_new values (3,333); +end; +drop foreign table test_new; +--T12 Inserts Tests +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null, data int, +primary key(x) +); +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=4; + +--T13 Inserts Tests II +drop foreign table test_new_gc; +create foreign table test_new_gc (x int, y int not null, z int not null,data int, +primary key(x) +); + +create index idx1 on test_new_gc (y); +create unique index idx2 on test_new_gc (z); + +insert into test_new_gc (x,y,z) values (1,2,3); +begin; +delete from test_new_gc where x=1; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +insert into test_new_gc (x,y,z) values (1,2,4); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +delete from test_new_gc where y=2; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=4; +insert into test_new_gc (x,y,z) values (1,2,5); +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +select * from test_new_gc where z=5; +delete from test_new_gc where z=5; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=5; +insert into test_new_gc (x,y,z) values (1,2,100); +commit; +select * from test_new_gc where x=1; +select * from test_new_gc where y=2; +select * from test_new_gc where z=3; +select * from test_new_gc where z=4; +select * from test_new_gc where z=100; +drop foreign table test_new_gc; + +-- bug 150 +create foreign table test_new (x integer primary key, y integer not null, c1 varchar(1020), c2 varchar(1020)) ; +create index idx on test_new (x,y); +begin; +insert into test_new values (generate_series(1, 500), generate_series(1, 500)); + +delete from test_new where x = 15; +insert into test_new values (15,15); +insert into test_new values (15,15); +end; +drop foreign table test_new; + +-- bug 170 +create foreign table test_new (i int primary key, x int not null); +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; + +begin; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; +drop foreign table test_new; + +-- bug 169 +create foreign table test_new (i int primary key, x int not null); +insert into test_new values (generate_series(1,100)); +begin; +update test_new set x = i+1; +delete from test_new where i = 3; +insert into test_new values (3,333); +commit; +delete from test_new where i = 3; +drop foreign table test_new; diff --git a/src/test/regress/sql/mot/single_sp_queries_rtd.sql b/src/test/regress/sql/mot/single_sp_queries_rtd.sql new file mode 100644 index 000000000..603569e1e --- /dev/null +++ b/src/test/regress/sql/mot/single_sp_queries_rtd.sql @@ -0,0 +1,268 @@ +create foreign table customer ( +c_w_id integer not null, +c_d_id integer not null, +c_id integer not null, +c_discount decimal(4,4), +c_credit char(2), +c_last varchar(16) not null, +c_first varchar(16) not null, +c_credit_lim decimal(12,2), +c_balance decimal(12,2), +c_ytd_payment decimal(12,2), +c_payment_cnt integer, +c_delivery_cnt integer, +c_street_1 varchar(20), +c_street_2 varchar(20), +c_city varchar(20), +c_state char(2), +c_zip char(9), +c_phone char(16), +c_since timestamp, +c_middle char(2), +c_data varchar(500), +primary key (c_w_id, c_d_id, c_id) +); + + + +insert into customer values(1,1,1,.4819,'GC','BARBARBAR','K1qXJYUx8',50000.00,-10.00,10.00,1,1,'GzRriVYf8PkgzfDWHo','hw5XQWK43NYR','S4qUZfkIBvp7HIgdYEPc','RF',637211111,3140408377069331,'2021-05-03 18:17:11.558','OE','qJ4LHCniHIiv5dnReoE36YzJnndwpSTXp2VWCPcVQYJ2SZxJ6RfQAt6zs3wwyl3rEDeoo5UEAzZKndJaqaU5I27X1u0jNUUXGtnM24Q9i4L3rY9ysXzokgB0eReIBPHmcizS7qtULcIy8cyD3FrmsvVgABPeocvNSwltSxLkXm0G9yI2Z9lTmWQomiDWoYDGQtCKxxqwRFGz3KeKf8mh1HicQSPL57wP3cC5SK3JPjhRMlldWrUzthBa6orKIJhpHkz4UrJCvce71o9lWE0DezPuMQZvVs3vHey3E9D8my9ZhvnphCROC8qJ2pIQ7Jv0asc5kUCFPkOrLz9KOmYnsP80KFVy377AMjuFTnTtXCExxDPjCJaJf1exzgfcBGLMxDjcw1HX7f4beI10GyX2P2KOuE'); +insert into customer values(1,1,2,.1120,'GC','BARBAROUGHT','KhZaT7ZZgZoiU',50000.00,-10.00,10.00,1,1,'EhbkYYG1wX8CJ1sqFQn','ibBtwABlISHbmIS36Nq','zrj4DcMHmbwQF','FZ',735311111,2391423312654894,'2021-05-03 18:17:11.558','OE','Su57rgcoXVY8gVIYH68miuTOIsiMXMNuTh0AwX5lc3CVm7U5x1UJS6cqVypHanbxLmwbf9ru5tt2g2GzORMhojJW4CchtVlSCBor6D3ZO32vIVUmwpOtgV7ZfHsapanWZHlXPTf3it5fkUCctrAZZ5bCuxFCFq1HjDuGGQ3YfAap8NKyPGWdlZI4dexOjuQGHSGwaOV97WR1YhaahDG4Y4z3EdmLPkIutg5ATjDFFAYBIJmWPfx1scIEZJANfO2LMsJuORNA1pY5RcBgIE528LO4nji8QrGPuiIUgsftepu2YHLSD5OjEo3xiNotRJAwkB'); +insert into customer values(1,1,3,.4651,'BC','BARBARABLE','IeE0f3Mp',50000.00,-10.00,10.00,1,1,'QTCUXZXNkbyJE','ix2pRoBHnO','pk7FNxuq9mc','OG',818211111,9505990784961970,'2021-05-03 18:17:11.558','OE','YYp3gkM6KDTE9DjLmWoxUqYQzBYV9YGQx8OzAGd0JjAvujjvzdrA7jfpYRJHuIqGKHuNlzXT1Tl7wZfJknuUXfZUfohIJ3lxPMQxzt2xMKlNXweJ28bbfR5nWbLGnQviVAotqce8KTGrqyVCN38M7hibGRbogKa55t3Wu7XePi1Mghllws62kAKgvC9DaWojjOcFXXnKI1bZgMjoDeVxZFATn7tCBVIVDh8kUR7SkJ1ZYjykbH9Iyi62vL497aOeqjRRa8dSoYNdgwxcUv891p1KyVgSGAMU8IfBnMyfG5wB0Ow8Rfkr9XedEk4B7R03bNhFUAfVF7L81z9EjFSc44SKwpovXBMGubxQz7fnEc9JDbCd2KIGpetqoeqwkmkAHyCbGfJP8GZGZoioTmLNNasvsotbRh7S1sqM2mI4qtRHOoJ9mKfY1WW5Ui2wMxWKQNI10s7VovXfSfgO'); +insert into customer values(1,1,4,.0141,'GC','BARBARPRI','XmyH4CJm3GTVRan',50000.00,-10.00,10.00,1,1,'DDgvLy5wBLRh5zX7ru2','LxqcrPJyQO8U','ogPkYtXVquW0N','FU',552811111,9444908480678354,'2021-05-03 18:17:11.558','OE','pF8XO8imui000TS00FSydSJzZAxPzJXSpTFjnJLlXF1C8rYY8QXtNyNN9ouRQScd80tbuAtcSYTEQPgxUgd9u79e69qBlPDDYGTISqBwG90OTEzFrakWyzySTASQeIRYbe3VPgiuRcMwybGoipZLbDHNixlGSZZCeXkFEempaPDaDBIqtrdnq6LAZAaDN18wCgZjMAv2B7EGteg2UtdQswOFOwGbnwG4VsMbp7axJsM74ZlS4E92MllOZnWD0cHyGk6KG7wRqzQlQNmi1cQix9YiG6j813WqktJw629RmwwmpqTNJ7TAERKuRUItksVl3Hvx1lgloX7V5WfbHyaHd57i2kIqMY8zeoAacD0DEICzugH7yynutInaTpZovuPUADs7NdVy393HDLaKQpq1ZzYnP9hxLmDhU9zjs7a'); +insert into customer values(1,1,5,.1251,'GC','BARBARPRES','WEHHK7ZdvakFPwa',50000.00,-10.00,10.00,1,1,'WnrnoQKSED','GTFxzPEcua66HQVmeuWp','cxsKcj4zQEb3xnurFcz','EY',581311111,0980067796148934,'2021-05-03 18:17:11.558','OE','ckgfjVSUJgXdkTjOmAhfnDlUI3TWt3AMnxTu9BUlPan1c5GCRo4NQKDX9H1lCX312ZKGqjCHKWtPKgL1uDDw1xnbrK2xePuaLBd92w44ZrWX2F6PvnDyR0yPZqaAmuFNLxd7KKbWJp57RWQSZ1qrTfB1FXECeHCPWtpSZX2bL9ziWqXS1LQ7OUGo1PmG9CnlRRVK93Qh6wvCY9ruONB2mf1q0CH0kOL9IDdvNBrN5kfFND2ivFfaZbEGD8SaIaDmj41y9B2cOzy9rALeNMUURnhEnu7A8XhRpQcW0lzBdSishHC4iHMaFrgnYw3cYUJnPjBt9n6JxIKOQx4u3qHWytXIco4PLoD4aN7z4ERYRxXDayTqgg4Yj0cpP1EmkstnXcYj6JDzgkCCM4Nofn5g5BS1vxVGXBAyYwooQ2XagDXfDdjhLXB5dL379BUFOlNMIatk'); +insert into customer values(1,1,6,.2541,'GC','BARBARESE','ZoeeBnnU1Zop6ML',50000.00,-10.00,10.00,1,1,'tGTUqTtjP1P0W','yUty9WfLIrslZUX','XpkIRU7D1rOt9ev95V6','NJ',093311111,0381242010918520,'2021-05-03 18:17:11.558','OE','KBRveK8nbwgb87VuhY9T2YsQoBEemGvuwQisgsXk8iJuVFwvF75aL0SOQi0iGIOHIDUAvznb5QVmzq4sY7cAG5gElx8DXvYa1lA3qMVDUSBtdgTYNxWchbjGbMuXOQdqOylH3Bo9CvDyoaLvyjg2KvkzRheGpTgaJFCEq4eKBVxsIt7biiqzoPvDXIuqcMpuYjcT86UtwOuuJRRWuEliL7ehviiY6WhqEc7TX6zORSpJ3hCwsaMRLnurwZFcFGHxUxfboDB91nm7N5ydJROzUz5qmafczmw5SPEONkkuiPnEeffMtKCgyXqjR2FIjSaXXWEyHGdkoDUd3yNX3PgQxFK8vIiLEIGNl5CrHnoDZE41v9WEAVi4MqesWdqcbguxdohuAXQhH4aZRNJLoQb2o8X5bQJccTLqzEVZVa58ZePzeTG6NUvDxVMSQPp7JAYqGtzPO0PpsBvpXj9ixIY0vbEjIJV5nPBLuiondBvfFPa'); +insert into customer values(1,1,7,.2211,'GC','BARBARANTI','yP3UO14nDcKVWT',50000.00,-10.00,10.00,1,1,'CsRfGWBSiHsM48m','nQcWfwSnQ8rx','s3BiVOmYlUVDULfUd5Sh','NJ',986511111,9852899873047153,'2021-05-03 18:17:11.558','OE','D2HYFitKPl71BaupGFL7dpmvCZBF9sVeKrCZtQCq3tjuHRPAiRUolinxKqGqygM9ifnwULISJR9U2l5CwpELoyFetm5HJnAo9MM7sdcpyZyrb60hk5lZhTcjvlqTCsA2jcn7b4Q6aI20tyxwZLwD8G5lWvRFR9VpKbQZyVNwlR1yUPn5iXTYJemslhS35jNfVympGSPtt42RZz3pl3Y7iLAg3naB4E9tX0rLCyix0pjNJ1pEJVZkE0lUdLB4BSv7qBTRgl6NytO5Pvvvvq9vY13lHH8uBcK5Snv8pcakmtWNqxFUMkVIkQmpQywWKWQbvzvN9v9MKCT6y6O2x7vRADDmLC5QvA4pMwmR5dshCVUoqLYAS1YxWo2kkGdGLYKooLjXjLCMt'); +insert into customer values(1,1,8,.4275,'GC','BARBARCALLY','AmIEo5hro',50000.00,-10.00,10.00,1,1,'T38BcTmUfrfwlVE','k7fJJVbI5PEH8dwv3zdd','MSAOPNhZbRlT2jMJG','BH',312011111,9652687324079719,'2021-05-03 18:17:11.558','OE','BEQM7mOJK9pDfNqnLF7ppP36SiV0widFPOT6DU3YY0VFnxP5veTUngwPtlxVgF7t2G8a8PTxHRw3GNb1RrYRiR13uBd49Mqzmtee37LUyCPGyyze5ZQtTvRdhun2Vucfwv1R4esQ6bPoAgnteYLyWzBXBod2ZNOruLNNqfm3r9OUOyDDkzz69wpEKxa6MXISoSe9OrfkBKiza76N09YowaM1s10IsIrqSDW6HhW91hszYemTjJRiUUSo72tW9mTTZiVrtGYkrDyYbBBKhqtdPasJ7I6SUkEtFyM77WFYSsPrVGG2Dd9AWvCN6eby8Cpvbwpo6egBqQq2ckZkTlbOPmde2r4XwAr5lmOOVo63ebUOJD7l5LQp4tNUYMn6UxLH5MaUizguaISlgjYqykKfx5sz5Fh5LPNmY5HiVGcc9JYEPpT'); +insert into customer values(1,1,9,.0230,'BC','BARBARATION','o7S4i3Fyzj7CE',50000.00,-10.00,10.00,1,1,'uew980V3LMjRWlaMkr','DbXnfpT5t9gh','L2QLwQzOZkX','EQ',741911111,1093277057883824,'2021-05-03 18:17:11.558','OE','dZrWWxARghTi7R3cF8CwnOUkZtmVVQLZvvZpPFOaZnqgPA9G2FLG2Vr7F6h7o2wlkOUiv67xxS1TiZ1HNhFBe8H0w1xNTXyG2Hj0pXJZ1ArSXufD0KTpJmcIQqRshwEIa8lHG34LA0mnexakbU0nkMgRSam8EZyES2o9GFcDmmSVtM3hmrC7jOuI2ggJo3uMd1QrViWh9FltJHiENTyRfV880oRBtcKevmG1S3CTedq4XPQQwBKK6Bm3uBiPfiby6dIeYNeMEPcoM7C0W5y4llCR0nWwFhpOFi3SrmWZ5JJhanfLgSQTjdLrfBZo9yjrPSFQjLF2cbTQAgWf'); + + +drop procedure if exists functions; + +-- max, min, avg, count, left, right, update, select, substring, coalesce, extract, epoch, concat, rtrim, abs, position, order by +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 integer, OUT out4 timestamp, OUT out5 timestamp, +OUT out6 decimal, OUT out7 decimal, OUT out8 character varying, OUT out9 character varying, OUT out10 integer, OUT out11 character varying, +OUT out12 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + + SELECT COUNT(*) INTO v_count1 from customer where c_credit like 'G%'; + out1 := v_count1; + + SELECT COUNT(*) INTO v_count2 FROM customer + where c_w_id = 1 + and c_d_id < 10 + and c_id <> 100 + and c_credit like 'G%' + GROUP BY c_id + ORDER BY c_id + LIMIT 1; + out2 := v_count2; + + select count( distinct c_id) into out3 from customer where c_d_id <10; + select max( c_since) into out4 from customer; + select min( c_since) into out5 from customer; + select avg( c_discount) into out6 from customer; + update customer set c_credit_lim = c_balance - c_ytd_payment where c_d_id <> 5; + select count(*) into out7 from customer where c_d_id BETWEEN 6 AND 9; + + select SUBSTRING( c_first, 1, 5 ) into out8 from customer where c_balance = (select max(c_balance) from customer) order by 1 limit 1; + select COALESCE (c_first, LEFT(c_data, 15)) into out9 from customer order by c_first desc limit 1; + select extract(epoch from c_since) - extract(epoch from c_since - 10) into out10 from customer limit 1; + select concat((select left(c_data,5) from customer order by c_first desc limit 1), (select right(c_data,5) from customer order by c_first asc limit 1)) into out11 from customer limit 1; + select count (rtrim (c_phone, '260944')) into v_count2 from customer; + select max(abs(POSITION('XXX' IN c_data))) into out12 from customer; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select functions(); +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + + +-------------------------------------------------------------------------- +-- sum, ceiling, char_length, length, cos, lower, upper, mod +drop procedure if exists functions; + +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 integer, OUT out4 numeric, OUT out5 integer, +OUT out6 character varying, OUT out7 character varying, OUT out8 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + select sum(c_id) into out1 from customer; + SELECT CEILING(c_discount + 9.76) into out2 from customer order by c_discount desc limit 1; + SELECT char_length(c_data) into out3 from customer order by c_data desc limit 1; + SELECT length(c_data) into out4 from customer order by c_data desc limit 1; + select cos(c_balance) into out5 from customer limit 1; + select lower(c_first) into out6 from customer where c_id = 1 order by c_first limit 1; + select upper(c_first) into out7 from customer where c_id = 1 order by c_first limit 1; + select mod((select sum(c_id) from customer), 3) into out8 from customer limit 1; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select functions(); +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + +------------------------------------------------------------------------- +-- REGEXP_MATCHES, sqrt, +drop procedure if exists functions; + +CREATE OR REPLACE PROCEDURE functions(OUT out1 character varying, OUT out2 integer, OUT err_msg character varying) +AS +DECLARE + v_count1 integer; + v_count2 integer; +BEGIN + SELECT REGEXP_MATCHES(c_data, '([A-Za-z0-9_]+)','g') into out1 from customer order by c_data desc limit 1; + select sqrt((select sum(c_id) from customer)) into out2 from customer limit 1; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select functions(); +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + +------------------------------------------------------------------------ + +-- create, update, delete, loop, extract, day, month, year, left join, right join, inner join, IN +drop procedure if exists functions; + +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 character varying, OUT out3 character varying, OUT out4 character varying, OUT out5 integer, OUT out6 integer, +OUT out7 integer, OUT out8 integer, OUT out9 integer, OUT out10 integer, OUT err_msg character varying) +AS +DECLARE + counter integer := 0; + v_count2 integer := 0; + vsum integer := 0; +BEGIN + create foreign table if not exists tmp (like customer); + insert into tmp select * from customer; + update tmp set c_balance = c_balance + c_credit_lim where (c_d_id = 2 or c_d_id = 3) and c_credit like 'G%'; + delete from tmp where (c_d_id = 2 or c_d_id = 3) and c_credit not like 'G%'; + + select count (*) into v_count2 from tmp where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%'; + + WHILE TRUE LOOP + counter := counter + 1; + IF counter = v_count2 THEN + update tmp set c_balance = c_balance + c_credit_lim where (c_w_id = 1 and c_d_id = counter and c_id = vsum); + delete from tmp where (c_d_id = 5 or c_id = 3) and c_credit like 'G%'; + EXIT; + END IF; + + vsum := vsum + counter; + END LOOP; + out1 = vsum; + + + SELECT EXTRACT(YEAR FROM c_since) into out2 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + SELECT EXTRACT(MONTH FROM c_since) into out3 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + SELECT EXTRACT(DAY FROM c_since) into out4 from customer where (c_d_id = 1 or c_d_id = 2) and c_credit not like 'G%' order by c_data limit 1; + + select count(*) into out5 from customer left join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out6 from customer right join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out7 from customer inner join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out8 from customer left outer join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + select count(*) into out9 from customer right outer join tmp on customer.c_w_id = tmp.c_id and customer.c_d_id = tmp.c_w_id and customer.c_id = tmp.c_id and customer.c_credit = tmp.c_credit and tmp.c_credit not like 'G%' ; + + select count(*) into out10 from tmp where c_w_id IN (1,2,3) and c_d_id IN (3,2,1) and c_id IN (4,5,6); + drop foreign table tmp; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select functions(); +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + +------------------------------------------------------------------------ +drop procedure if exists functions; + +-- for loop, while loop, cast, delete using, +CREATE OR REPLACE PROCEDURE functions(OUT out1 integer, OUT out2 integer, OUT out3 character varying, OUT out4 integer, OUT out5 integer, OUT err_msg character varying) +AS +DECLARE + counter integer := 0; + total_balance integer := 0; + balance integer := 0; + genre_rec character varying; + v_loop integer := 0; + v_out integer; +BEGIN + + create foreign table if not exists tmp (like customer); + create index on tmp(c_w_id, c_d_id, c_id); + insert into tmp select * from customer; + select count(*) into counter from customer; + out1 := counter / 100; + + FOR i IN 1..out1 LOOP + delete from tmp using customer where tmp.c_w_id = customer.c_w_id and tmp.c_d_id = customer.c_d_id and tmp.c_id = customer.c_id limit 1; + END LOOP; + select count (*) into out2 from tmp; + + WHILE TRUE LOOP + IF v_loop >= out1 THEN + EXIT; + END IF; + + IF ((v_loop % 2) = 1) THEN + update tmp set c_balance = c_balance + 1 where c_w_id % 2 = 1 and c_d_id % 2 = 1 and c_id = 1; + ELSE + insert into tmp select * from customer where c_w_id % 2 = 1 and c_d_id % 2 = 1 and c_id % 2 = 1 limit 1; + END IF; + + v_loop := v_loop + 1; + END LOOP; + + select count (*) into v_out from tmp; + + select CAST (v_out AS character varying) into out3; + out4 := out3::integer; + + update customer set c_delivery_cnt = c_delivery_cnt + c_payment_cnt + c_discount where c_balance != 0 and c_city like '%y' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13 and c_id in (131,295.665); + + select count (*) into out5 from customer where c_balance != 0 and c_city like '%y%' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13 and c_id in (select c_id from customer where c_city like '%y%' and c_first like '%a%' and c_middle not like '%w%' and c_w_id <> 13); + drop foreign table tmp; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select functions(); +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='query_count'; +--select jittable_status from mot_jit_detail() where namespace='TEST' and proc_id=9999 and proc_name='function_count'; + + +drop foreign table customer; +drop procedure if exists functions; diff --git a/src/test/regress/sql/mot/single_sp_queries_rtd2.sql b/src/test/regress/sql/mot/single_sp_queries_rtd2.sql new file mode 100644 index 000000000..d4bbed7fa --- /dev/null +++ b/src/test/regress/sql/mot/single_sp_queries_rtd2.sql @@ -0,0 +1,147 @@ +------------------------------------------------------------------------------------ +create foreign table table1 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020)); +create foreign table table2 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020)); + +CREATE OR REPLACE PROCEDURE random_text_simple(length INTEGER, out random_text_simple TEXT) + AS + DECLARE + possible_chars TEXT := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + output TEXT := ''; + i INT4; + pos INT4; + BEGIN + + FOR i IN 1..length LOOP + pos := random_range(1, length(possible_chars)); + output := output || substr(possible_chars, pos, 1); + END LOOP; + + random_text_simple := output; + END; +/ + +CREATE OR REPLACE FUNCTION random_range(INTEGER, INTEGER) + RETURNS INTEGER + LANGUAGE SQL + AS $$ + SELECT ($1 + FLOOR(($2 - $1 + 1) * random() ))::INTEGER; +$$; + +CREATE OR REPLACE PROCEDURE insert_into_table1(max integer, OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table1 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable1 from table1; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +CREATE OR REPLACE PROCEDURE insert_into_table2(max integer, OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table2 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable1 from table2; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +select insert_into_table1(1000); +select insert_into_table2(1000); + +CREATE OR REPLACE PROCEDURE insert_into(max integer, OUT outable1 integer, OUT outable2 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; +BEGIN + insert into table1 values (generate_series(1,v_max)); + insert into table2 select * from table1 where c1 is null; + select count(*) into outable1 from table1; + select count(*) into outable2 from table2; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ + +select insert_into(1000); + + +CREATE OR REPLACE PROCEDURE delete_from(OUT outable1 integer, OUT outable2 integer, OUT err_msg character varying) +AS +DECLARE + t_record record; +BEGIN + create index on table1(x); + create index on table2(x); + truncate table1; + truncate table2; + select insert_into_table1(1000) into t_record; + select insert_into_table2(1000) into t_record; + select insert_into(1000) into t_record; + delete from table1 using table2 where table1.x = table2.x and table1.c1 = table2.c2; + delete from table2 using table1 where table1.x = table2.x and table1.c1 != table2.c2; + + select count (*) into outable1 from table1; + select count (*) into outable2 from table2; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ + +select delete_from(); + +CREATE OR REPLACE PROCEDURE compare(OUT outable1 integer, OUT err_msg character varying) +AS +DECLARE + t_record record; +BEGIN + select count (*) into outable1 from table1,table2 where table1.x = table2.x and table1.c1 like '%%%' and table2.c1 like '%%%'; + +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ + +select compare(); + +drop foreign table table1; +drop foreign table table2; + diff --git a/src/test/regress/sql/mot/single_sp_queries_rtd3.sql b/src/test/regress/sql/mot/single_sp_queries_rtd3.sql new file mode 100644 index 000000000..2d4cd0c5f --- /dev/null +++ b/src/test/regress/sql/mot/single_sp_queries_rtd3.sql @@ -0,0 +1,155 @@ +SET check_function_bodies = false; + +drop foreign table if exists table111; +create foreign table table111 (x integer not null, c1 varchar(1020), c2 varchar(1020), c3 varchar(1020), c4 varchar(1020), c5 varchar(1020), primary key(x)); + +CREATE OR REPLACE PROCEDURE random_text_simple(length INTEGER, out random_text_simple TEXT) + AS + DECLARE + possible_chars TEXT := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + output TEXT := ''; + i INT4; + pos INT4; + BEGIN + + FOR i IN 1..length LOOP + pos := random_range(1, length(possible_chars)); + output := output || substr(possible_chars, pos, 1); + END LOOP; + + random_text_simple := output; + END; +/ + +CREATE OR REPLACE FUNCTION random_range(INTEGER, INTEGER) + RETURNS INTEGER + LANGUAGE SQL + AS $$ + SELECT ($1 + FLOOR(($2 - $1 + 1) * random() ))::INTEGER; +$$; + +CREATE OR REPLACE PROCEDURE build(max integer, OUT outable111 integer, OUT err_msg character varying) +AS +DECLARE + v_max integer := max; + c1 character varying; + c2 character varying; + c3 character varying; + c4 character varying; + c5 character varying; +BEGIN + FOR i IN 1..v_max LOOP + select random_text_simple(5) into c1; + select random_text_simple(5) into c2; + select random_text_simple(5) into c3; + select random_text_simple(5) into c4; + select random_text_simple(5) into c5; + insert into table111 values (i, c1, c2, c3, c4, c5); + END LOOP; + select count(*) into outable111 from table111; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; + +END; +/ + +CREATE OR REPLACE PROCEDURE delfunc(id numeric) AS + BEGIN + DELETE FROM table111 + WHERE x = id; + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ + +CREATE OR REPLACE PROCEDURE updatefunc(id numeric) AS + DECLARE + v_c1 character varying; + v_c2 character varying; + v_c3 character varying; + v_c4 character varying; + v_c5 character varying; + BEGIN + select random_text_simple(5) into v_c1; + select random_text_simple(5) into v_c2; + select random_text_simple(5) into v_c3; + select random_text_simple(5) into v_c4; + select random_text_simple(5) into v_c5; + UPDATE table111 set c1 = v_c1, c2 = v_c2, c3 = v_c3, c4 = v_c4, c5 = v_c5 + WHERE x = id; + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ + +CREATE OR REPLACE PROCEDURE insertfunc(id numeric) AS + DECLARE + t_record record; + BEGIN + INSERT INTO table111 VALUES (id); + select updatefunc(id) into t_record; + + EXCEPTION + WHEN OTHERS + THEN + return; + END; +/ + +CREATE OR REPLACE PROCEDURE bench(length INTEGER, OUT outable111 character varying, OUT err_msg character varying) +AS + DECLARE + i integer; + var_case integer; + t_record record; + count_all integer; + distinct_count integer; + res character varying; + BEGIN + + FOR i IN 1..length LOOP + SELECT floor(random()*(3-1+1))+1 into var_case; + + CASE var_case + WHEN 1 THEN select insertfunc(i+100) into t_record; + WHEN 2 THEN select delfunc(i) into t_record; + WHEN 3 THEN select updatefunc(i) into t_record; + END CASE; + END LOOP; + + select count (*) from table111 into count_all; + select count (distinct(x)) from table111 into distinct_count; + + IF count_all = distinct_count + THEN res := 'SUCCESS'; + ELSE res := 'FAIL'; + END IF; + outable111 := res; +EXCEPTION + WHEN OTHERS THEN + err_msg := SQLERRM; +END; +/ + + +select build(100); +select bench(100); + +drop foreign table table111; + + + + + + + + + + + + diff --git a/src/test/regress/sql/mot/single_sp_queries_rtd5.sql b/src/test/regress/sql/mot/single_sp_queries_rtd5.sql new file mode 100644 index 000000000..f25b26ff9 --- /dev/null +++ b/src/test/regress/sql/mot/single_sp_queries_rtd5.sql @@ -0,0 +1,92 @@ +create foreign table t_auto_call( + SM_USER_ID int not null primary key, + ROSTER_ID varchar (48), + DD_APPDATE date not null + ); + +create foreign table t_prmi_transaction( + SM_USER_ID int not null, + SD_KEY int not null, + SEQ_NO varchar(32), + SD_PT_SRV_ENTRY_NDE_L2 varchar(2), + SD_PT_SRV_COND_CDE varchar(4), + SD_TERM_CNTRY varchar(4), + SD_ACO_INST_ID_NUM_R4 int, + DD_APPDATE date not null + ); + +CREATE OR REPLACE PROCEDURE select_count(ev_ddApdate date, en_smUserId int, ev_sdKey int, ev_sdTermCntry character varying, ev_sdPtSrvCondCde varchar, +ev_sdAcqInstIdNumR4 int, result_type varchar, flag char, send_user varchar, OUT rv_autocall_wk_24h_abroadcntry INTEGER, OUT msg character varying) + AS +BEGIN + select count (1) into rv_autocall_wk_24h_abroadcntry + from T_AUTO_CALL t_auto_call, T_PRMI_TRANSACTION t_prmi_transaction + where t_auto_call.SM_USER_ID = en_smUserId + and t_prmi_transaction.SM_USER_ID = en_smUserId + and t_prmi_transaction.SD_KEY = ev_sdKey + and result_type = 'OK' + and flag = 'I' + and (send_user = 'PRMI' or right( roster_id, 9) = 'MANU_CALL') + and left(t_auto_call.ROSTER_ID, 32) = t_prmi_transaction.SEQ_NO + and t_prmi_transaction.SD_PT_SRV_ENTRY_NDE_L2 not in ('01','02','81','10','04','94') + and t_prmi_transaction.SD_PT_SRV_COND_CDE in ('V001','M001','A001','J001','N012','A003') + and ( + t_prmi_transaction.SD_TERM_CNTRY = ev_sdTermCntry + and ev_sdPtSrvCondCde != 'NO12' + and t_prmi_transaction.SD_PT_SRV_COND_CDE = 'N012' + or + t_prmi_transaction.SD_ACO_INST_ID_NUM_R4 = ev_sdAcqInstIdNumR4 + and t_prmi_transaction.SD_PT_SRV_COND_CDE = 'N012' + and ev_sdPtSrvCondCde = 'N012' + ) + and extract(second from ev_ddApdate) <= extract(second from t_prmi_transaction.DD_APPDATE) + 3600*24 + --and since_epoch(second, cast(ev_ddApdate as timestamp)) <= since_epoch(second, cast(t_prmi_transaction.DD_APPDATE as timestamp)) + 3600*24 + and extract(second from ev_ddApdate) <= extract(second from t_auto_call.DD_APPDATE) + 3600*24; + --and since_epoch(second, cast(ev_ddApdate as timestamp)) <= since_epoch(second, cast(t_auto_call.DD_APPDATE as timestamp)) + 3600*24; + + msg := 'OK'; +EXCEPTION + WHEN OTHERS THEN + msg := SQLERRM; +END; +/ + +create index on t_auto_call(DD_APPDATE); +create index on t_prmi_transaction(SM_USER_ID); +create index on t_prmi_transaction(SD_KEY); +create index on t_prmi_transaction(DD_APPDATE); + +insert into t_auto_call values (1, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); +insert into t_auto_call values (2, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); +insert into t_auto_call values (3, 'qwertyuiopasdfghjklzxcvbnmqwertyuiopas', current_date - '1 day'::INTERVAL); + +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '02', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 2, 'qwertyuiopasdfghjklzxcvbnmqwerty', '81', 'V001', 'XXZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (2, 1, 'qwertyuiopasdfghjklzxcvbnmqwery', '04', 'J001', 'XYY', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 3, 'qwertyuiopasdfghjklzxcvbnmqwerty', '94', 'A003', 'XZZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '01', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); + +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '07', 'N012', 'XYZ', 1, current_date - '1 day'::INTERVAL); +insert into t_prmi_transaction values (1, 1, 'qwertyuiopasdfghjklzxcvbnmqwerty', '00', 'N011', 'XYZ', 1, current_date - '1 day'::INTERVAL); + +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,2,'XYY','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,2,1,'XZZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,3,1,'XXZ','N012',1,'OK', 'I', 'PRMI'); + +select select_count(current_date,1,1,'XYZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XZ','N012',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XZ','N017',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'i', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N017',1,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N012',2,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N012',10,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYZ','N0123',10,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYzZ','N0123',10,'OK', 'I', 'PRMI'); +select select_count(current_date,1,1,'XYzZ','N0123',10,'OK', 'I', 'PMI'); + +drop foreign table t_auto_call; +drop foreign table t_prmi_transaction; diff --git a/src/test/regress/sql/mot/single_sp_queries_rtd6.sql b/src/test/regress/sql/mot/single_sp_queries_rtd6.sql new file mode 100644 index 000000000..c46cd7fdd --- /dev/null +++ b/src/test/regress/sql/mot/single_sp_queries_rtd6.sql @@ -0,0 +1,106 @@ +create foreign table t_login( + pid integer, + PHY_ID integer, + BSS_ID varchar(16), + DD_APDATE timestamp, + seq_No integer, + primary key (pid, seq_No) +); + +create foreign table t_name_list( + obj_no varchar(16), + obj_quality char, + expire_date timestamp +); + +CREATE OR REPLACE PROCEDURE login(v_cnt integer, ev_pid integer, ev_phyId integer, ev_ddApdate timestamp with time zone, out rv_loginWiFiwhiteList TEXT) + AS + DECLARE + v_loop integer := 0; + v_timestamp timestamp; + v_seqNo integer; + v_bssid varchar(16); + v_flag integer; + BEGIN + rv_loginWiFiwhiteList := ''; + /*if connected times more than 0*/ + if v_cnt > 0 then + rv_loginWiFiwhiteList := 'Y'; + --RAISE EXCEPTION 'if v_cnt > 0, v_cnt = (%), rv_loginWiFiwhiteList := Y', v_cnt; + else + /*check if connected on last week*/ + select count(1) into v_cnt + from t_login + where pid = ev_pid + and PHY_ID = ev_phyId + and BSS_ID like 'd4:68:ba%' + and extract(second from ev_ddApdate) - extract(second from DD_APDATE) <= 604800; + --and since_epoch(second, ev_ddApdate) - since_epoch(second, DD_APDATE) <= 604800; + /*if not connected on last week rv_loginWiFiwhiteList = N*/ + if (v_cnt = 0) then + rv_loginWiFiwhiteList := 'N'; + --RAISE EXCEPTION 'if v_cnt = 0, v_cnt = (%), rv_loginWiFiwhiteList := N', v_cnt; + else + --RAISE EXCEPTION ' else, v_cnt = (%)', v_cnt; + v_loop := 0; + v_timestamp := ev_ddApdate; + + while (v_loop < v_cnt + and (rv_loginWiFiwhiteList != 'Y' or rv_loginWiFiwhiteList is null or rv_loginWiFiwhiteList = '')) loop + + select seq_No into v_seqNo + from t_login + where pid = ev_pid + and PHY_ID = ev_phyId + and BSS_ID like 'd4:68:ba%' + and extract(second from ev_ddApdate) - extract(second from DD_APDATE) <= 604800 + --and since_epoch(second, ev_ddApdate) - since_epoch(second, DD_APDATE) <= 604800 + and DD_APDATE < v_timeStamp + order by DD_APDATE desc limit 1; + + select DD_APDATE into v_timeStamp + from t_login + where pid = ev_pid + and seq_No = v_seqNo order by DD_APDATE limit 1; + + select BSS_ID into v_bssid + from t_login + where pid = ev_pid + and seq_No = v_seqNo ; + + select count(1) into v_flag + from t_name_list + where obj_no = v_bssid + and obj_quality = 'w' + and expire_date > ev_ddApdate; + + if (v_flag > 0) then + rv_loginWiFiwhiteList := 'Y'; + --RAISE EXCEPTION ' if(v_flag > 0), v_flag = (%)', v_flag; + end if; + + v_loop := v_loop + 1; + end loop; + + if (v_loop = v_cnt + and (rv_loginWiFiwhiteList != 'Y' or rv_loginWiFiwhiteList is null or rv_loginWiFiwhiteList = '')) then + rv_loginWiFiwhiteList := 'N'; + end if; + end if; + end if; +END; +/ + +insert into t_login values (1,1,'d4:68:ba%', current_timestamp, 1); +select login(1,1,1,current_timestamp); +insert into t_name_list values ('d4:68:ba%','y', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); +insert into t_login values (1,1,'d4:68:ba:dd%', current_timestamp, 2); +select login(0,1,1,current_timestamp); +insert into t_name_list values ('d4:68:ba:da','w', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); +insert into t_name_list values ('d4:68:ba%','w', current_timestamp + '1 day'::INTERVAL); +select login(0,1,1,current_timestamp); + +drop foreign table t_login; +drop foreign table t_name_list; diff --git a/src/test/regress/sql/mot/single_supported_unsupported_types.sql b/src/test/regress/sql/mot/single_supported_unsupported_types.sql index a0798f6ea..57b1291c5 100644 --- a/src/test/regress/sql/mot/single_supported_unsupported_types.sql +++ b/src/test/regress/sql/mot/single_supported_unsupported_types.sql @@ -16,6 +16,7 @@ create foreign table test11 (l lseg); create foreign table test12 (p polygon); create foreign table test13 (i inet); create foreign table test14 (m macaddr); +create foreign table test15 (t text); drop foreign table test0; drop foreign table test1; diff --git a/src/test/regress/sql/mot/single_update_secondary_index_column.sql b/src/test/regress/sql/mot/single_update_secondary_index_column.sql new file mode 100644 index 000000000..08dc15913 --- /dev/null +++ b/src/test/regress/sql/mot/single_update_secondary_index_column.sql @@ -0,0 +1,294 @@ +--T1 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; + +--T2 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +commit; + +--T3 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +begin; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +commit; + +--T4 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +commit; +select * from test_update_column; + +--T5 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +commit; +select * from test_update_column; + +--T6 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +insert into test_update_column values (3,3,4,100); +commit; +select * from test_update_column; + +--T7 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=5 where x=1; +select * from test_update_column; +insert into test_update_column values (3,3,4,100); +commit; +select * from test_update_column; + +--T8 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=5 where x=1; +select * from test_update_column; +insert into test_update_column values (3,3,4,100); +select * from test_update_column; +update test_update_column set y=100 where y=3; +commit; +select * from test_update_column; + +--T9 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +select * from test_update_column; +update test_update_column set z=5; +commit; +select * from test_update_column; + +--T10 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +alter foreign table test_update_column drop column y; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; + +--T11 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +alter foreign table test_update_column drop column z; +select * from test_update_column; + +--T12 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +alter foreign table test_update_column drop column z; +commit; +select * from test_update_column; + +--T13 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +update test_update_column set y=3 where x=1; +select * from test_update_column; +alter foreign table test_update_column drop column z; +update test_update_column set z=4 where x=1; +select * from test_update_column; +commit; +select * from test_update_column; + +--T14 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=4 where x=1; +select * from test_update_column; +alter foreign table test_update_column drop column z; +commit; +select * from test_update_column; + +--T15 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +begin; +select * from test_update_column; +update test_update_column set y=3 where x=1; +select * from test_update_column; +update test_update_column set z=5 where x=1; +select * from test_update_column; +insert into test_update_column values (3,3,4,100); +select * from test_update_column; +update test_update_column set y=100 where y=3; +select * from test_update_column; +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + +--T16 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +commit; +select * from test_update_column; + +--T17 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + + +--T18 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +commit; +select * from test_update_column; + +--T19 +drop foreign table test_update_column; +create foreign table test_update_column (x int primary key, y int not null, z int not null, data int); +create index idx1 on test_update_column (y); +create unique index idx2 on test_update_column (z); +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,4,100); +begin; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +delete from test_update_column; +insert into test_update_column values (1,2,3,100); +insert into test_update_column values (3,3,5,100); +update test_update_column set z=z+1; +alter foreign table test_update_column drop column y; +commit; +select * from test_update_column; + +drop foreign table test_update_column; From e71d571b6d94a0f84a5e055fd5f6062c55f8e648 Mon Sep 17 00:00:00 2001 From: Vinoth Veeraraghavan Date: Mon, 5 Dec 2022 11:27:19 +0800 Subject: [PATCH 2/2] Minor fixes --- .../mot/core/storage/sentinel/sentinel.h | 2 +- .../storage/mot/core/storage/table.h | 2 +- .../system/checkpoint/checkpoint_utils.cpp | 24 ++----------------- .../core/system/checkpoint/checkpoint_utils.h | 18 +------------- .../mot/core/system/transaction/txn.cpp | 4 ++-- .../storage/mot/fdw_adapter/mot_internal.cpp | 4 ++-- 6 files changed, 9 insertions(+), 45 deletions(-) diff --git a/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h index bd5234151..f0f26e31d 100644 --- a/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h +++ b/src/gausskernel/storage/mot/core/storage/sentinel/sentinel.h @@ -261,7 +261,7 @@ public: m_refCount = m_refCount & (~S_COUNTER_LOCK_BIT); #else - uint64_t v = GetRefCount(); + uint32_t v = GetRefCount(); while (!__sync_bool_compare_and_swap(&m_refCount, v, (v & ~S_COUNTER_LOCK_BIT))) { PAUSE v = GetRefCount(); diff --git a/src/gausskernel/storage/mot/core/storage/table.h b/src/gausskernel/storage/mot/core/storage/table.h index fc14b7bf5..e468dbfa4 100644 --- a/src/gausskernel/storage/mot/core/storage/table.h +++ b/src/gausskernel/storage/mot/core/storage/table.h @@ -592,7 +592,7 @@ public: * @brief Retrieves the number of fields (columns) in each row in the table. * @return Number of fields in a row. */ - inline uint64_t GetFieldCount() const + inline uint32_t GetFieldCount() const { return m_fieldCnt; } diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp index abe138efe..a1f17fe35 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.cpp @@ -54,26 +54,6 @@ bool IsDirExists(const std::string& dirName) return true; } -bool OpenFileWrite(const std::string& fileName, FILE*& pFile) -{ - pFile = fopen(fileName.c_str(), "wb"); - if (pFile == nullptr) { - MOT_REPORT_SYSTEM_ERROR(fopen, "N/A", "Failed to open file %s for writing", fileName.c_str()); - return false; - } - return true; -} - -bool OpenFileRead(const std::string& fileName, FILE*& pFile) -{ - pFile = fopen(fileName.c_str(), "rb"); - if (pFile == nullptr) { - MOT_REPORT_SYSTEM_ERROR(fopen, "N/A", "Failed to open file %s for reading", fileName.c_str()); - return false; - } - return true; -} - bool OpenFileWrite(const std::string& fileName, int& fd) { fd = open(fileName.c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); /* 0600 */ @@ -132,9 +112,9 @@ int FlushFile(int fd) return rc; } -bool SeekFile(int fd, uint64_t offset) +bool SeekFile(int fd, off64_t offset) { - int rc = lseek(fd, offset, SEEK_SET); + int rc = lseek64(fd, offset, SEEK_SET); if (rc == -1) { MOT_REPORT_SYSTEM_ERROR(write, "N/A", "Failed to seek file descriptor %d to offset %" PRIu64, fd); } diff --git a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h index 0122afa27..6e7cd38ac 100644 --- a/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h +++ b/src/gausskernel/storage/mot/core/system/checkpoint/checkpoint_utils.h @@ -77,22 +77,6 @@ bool IsFileExists(const std::string& fileName); */ bool IsDirExists(const std::string& dirName); -/** - * @brief A wrapper function that opens a file for writing. - * @param fileName The file name to open - * @param pFile The returned FILE* pointer - * @return Boolean value denoting success or failure. - */ -bool OpenFileWrite(const std::string& fileName, FILE*& pFile); - -/** - * @brief A wrapper function that opens a file for reading. - * @param fileName The file name to open - * @param pFile The returned FILE* pointer - * @return Boolean value denoting success or failure. - */ -bool OpenFileRead(const std::string& fileName, FILE*& pFile); - /** * @brief A wrapper function that opens a file for writing. * @param fileName The file name to open @@ -147,7 +131,7 @@ int FlushFile(int fd); * @param offset The offset in the file to seek to. * @return Boolean value denoting success or failure. */ -bool SeekFile(int fd, uint64_t offset); +bool SeekFile(int fd, off64_t offset); /** * @brief Frees a row's stable version row. diff --git a/src/gausskernel/storage/mot/core/system/transaction/txn.cpp b/src/gausskernel/storage/mot/core/system/transaction/txn.cpp index cd9207f88..b245819aa 100644 --- a/src/gausskernel/storage/mot/core/system/transaction/txn.cpp +++ b/src/gausskernel/storage/mot/core/system/transaction/txn.cpp @@ -2008,11 +2008,11 @@ RC TxnIxColUpdate::InitAndBuildOldKeys(Row* row) for (uint64_t i = 0; i < m_cols; i++) { if (BITMAP_GET(m_modBitmap, i)) { - if (m_tab->GetField((uint16_t)(i + 1))->IsUsedByIndex()) { + if (m_tab->GetField((i + 1))->IsUsedByIndex()) { for (uint16_t j = 0; j < m_arrLen; j++) { if (m_ix[j] == nullptr) { MOT::Index* ix = m_tab->GetIndex(j); - if (ix->IsFieldPresent((uint16_t)(i + 1))) { + if (ix->IsFieldPresent(static_cast(i + 1))) { m_ix[j] = ix; } } diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp index cca2f11ab..c1ae61c18 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_internal.cpp @@ -940,7 +940,7 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) stmt->idxname, stmt->indexOid, stmt->relation->relname); - uint64_t keyLength = 0; + uint32_t keyLength = 0; MOT::Index* index = nullptr; MOT::IndexOrder index_order = MOT::IndexOrder::INDEX_ORDER_SECONDARY; @@ -964,7 +964,7 @@ MOT::RC MOTAdaptor::CreateIndex(IndexStmt* stmt, ::TransactionId tid) return MOT::RC_ABORT; } index->SetExtId(stmt->indexOid); - if (!index->SetNumTableFields((uint32_t)table->GetFieldCount())) { + if (!index->SetNumTableFields(table->GetFieldCount())) { table->GetOrigTable()->Unlock(); delete index; report_pg_error(MOT::RC_ABORT);