diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a7c59f9..2438d144 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ option(BUILD_TESTS "Build test programs" ON) option(BUILD_EXAMPLES "Build example programs" ON) option(BUILD_BENCHMARK "Build benchmark programs" ON) option(BUILD_DOCUMENTS "build documents" ON) +option(BUILD_MOCK "Build mock server" OFF) option(INSTALL_EXAMPLES "install examples" OFF) option(BUILD_SHARED_LIBS "build shared libraries instead of static" ON) option(ENABLE_CACHE_ALIGN "enable optional cache align requirement" OFF) @@ -133,4 +134,7 @@ endif () if (BUILD_DOCUMENTS) add_subdirectory(doxygen) endif() +if(BUILD_MOCK) + add_subdirectory(mock) +endif() diff --git a/README.md b/README.md index d09ebb76..aa9f65fe 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,10 @@ available options: * `-DCMAKE_IGNORE_PATH="/usr/local/include;/usr/local/lib/"` - specify the libraries search paths to ignore. This is convenient if the environment has conflicting version installed on system default search paths. (e.g. gflags in /usr/local) * `-DBUILD_SHARED_LIBS=OFF` - create static libraries instead of shared libraries * `-DBUILD_TESTS=OFF` - don't build test programs +* `-DBUILD_EXAMPLES=OFF` - don't build example programs +* `-DBUILD_BENCHMARK=OFF` - don't build benchmark programs * `-DBUILD_DOCUMENTS=OFF` - don't build documents by doxygen * `-DINSTALL_EXAMPLES=ON` - install example applications -* `-DBUILD_BENCHMARK=ON` - build benchmark programs * `-DSHARKSFIN_IMPLEMENTATION=` - switch sharksfin implementation. Available options are `memory` and `shirakami` (default: `memory`) * `-DENABLE_ALTIMETER=ON` - turn on the `altimeter logging`. * `-DMC_QUEUE=ON` - use moody camel queue instead of tbb queue to store tasks in tateyama task scheduler. @@ -72,6 +73,8 @@ available options: * `-DENABLE_UB_SANITIZER=ON` - enable undefined behavior sanitizer (requires `-DENABLE_SANITIZER=ON`) * `-DENABLE_COVERAGE=ON` - enable code coverage analysis (requires `-DCMAKE_BUILD_TYPE=Debug`) * `-DTRACY_ENABLE=ON` - enable tracy profiler for multi-thread debugging. See section below. +* for developper only + * `-DBUILD_MOCK=ON` - build a mock server ### install diff --git a/include/tateyama/api/configuration.h b/include/tateyama/api/configuration.h index 0db5a807..3c678410 100644 --- a/include/tateyama/api/configuration.h +++ b/include/tateyama/api/configuration.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 Project Tsurugi. + * Copyright 2018-2025 Project Tsurugi. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -183,7 +183,7 @@ class whole { } } // this constructor works as if property file exists and its content is provided as istream - whole(std::istream& content, std::string_view default_property) : property_file_exist_(true) { + whole(std::istream& content, std::string_view default_property) { initialize(content, default_property); } // default_property can be empty only for test purpose @@ -335,15 +335,11 @@ class whole { BOOST_FOREACH(const boost::property_tree::ptree::value_type &v, default_tree_) { auto& dt = default_tree_.get_child(v.first); bool default_required = (v.first != "glog"); - if (property_file_exist_) { - try { - auto& pt = property_tree_.get_child(v.first); - map_.emplace(v.first, std::make_unique
(pt, dt, this, default_required)); - } catch (boost::property_tree::ptree_error &e) { - vlog_info_ << "cannot find " << v.first << " section in the input, thus we use default property only." << std::endl; - map_.emplace(v.first, std::make_unique
(dt, this, default_required)); - } - } else { + try { + auto& pt = property_tree_.get_child(v.first); + map_.emplace(v.first, std::make_unique
(pt, dt, this, default_required)); + } catch (boost::property_tree::ptree_error &e) { + vlog_info_ << "cannot find " << v.first << " section in the input, thus we use default property only." << std::endl; map_.emplace(v.first, std::make_unique
(dt, this, default_required)); } } diff --git a/mock/CMakeLists.txt b/mock/CMakeLists.txt new file mode 100644 index 00000000..06a21f8d --- /dev/null +++ b/mock/CMakeLists.txt @@ -0,0 +1,68 @@ +if(NOT TARGET sharksfin-${SHARKSFIN_IMPLEMENTATION}) + message(FATAL_ERROR "sharksfin implementation \"sharksfin-${SHARKSFIN_IMPLEMENTATION}\" not found") +endif() + +file(GLOB_RECURSE ProtoFiles RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.proto") + +# By default, PROTOBUF_GENERATE_CPP generates file path for .pb.cc as if they are in the same directory. +# Work-around this with PROTOBUF_GENERATE_CPP_APPEND_PATH +set(PROTOBUF_GENERATE_CPP_APPEND_PATH OFF) +PROTOBUF_GENERATE_CPP(GENERATED_PROTO_SRCS GENERATED_PROTO_HDRS ${ProtoFiles}) +add_custom_target(build_protos_for_mock DEPENDS ${GENERATED_PROTO_SRCS}) + +file(GLOB SOURCES + "tateyama/server/*.cpp" + "tateyama/service/*.cpp" + "jogasaki/api/impl/*.cpp" + "jogasaki/serializer/*.cpp" +) + +set_source_files_properties( + ${GENERATED_SQL_PROTO_SRCS} + PROPERTIES + GENERATED TRUE + COMPILE_FLAGS "-Wno-unused-parameter -Wno-array-bounds" +) + +add_executable(mock_server + ${SOURCES} + ${GENERATED_PROTO_SRCS} +) + +add_dependencies(mock_server + build_protos_for_mock + ) + +set_target_properties(mock_server + PROPERTIES + INSTALL_RPATH "\$ORIGIN" + RUNTIME_OUTPUT_NAME "mock_server" + ) + +target_include_directories(mock_server + PRIVATE ${CMAKE_BINARY_DIR}/src + PRIVATE ${CMAKE_BINARY_DIR}/mock + PRIVATE ${CMAKE_SOURCE_DIR}/src + PRIVATE . +) + +target_link_libraries(mock_server + PUBLIC api + PRIVATE tateyama-impl + PRIVATE takatori + PRIVATE Boost::boost + PRIVATE Boost::filesystem + PRIVATE Boost::thread + PRIVATE Boost::container + PRIVATE glog::glog + PRIVATE protobuf::libprotobuf + ) + +# Boost.Thread doesn't seem to allow multiple versions to coexist. +# This version definition should be shared with caller at least. +target_compile_definitions(mock_server PUBLIC BOOST_THREAD_VERSION=4) + +set_compile_options(mock_server) + +# target_name = mock_server, export_name = mock_server +install_custom(mock_server mock_server) diff --git a/mock/jogasaki/proto/sql/common.proto b/mock/jogasaki/proto/sql/common.proto new file mode 100644 index 00000000..3d811f70 --- /dev/null +++ b/mock/jogasaki/proto/sql/common.proto @@ -0,0 +1,276 @@ +syntax = "proto3"; + +package jogasaki.proto.sql.common; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.sql.proto"; +option java_outer_classname = "SqlCommon"; + +/* + * Common. + */ + +/* For session handle. */ +message Session { + uint64 handle = 1; +} + +/* For transaction handle. */ +message Transaction { + uint64 handle = 1; +} + +/* For transaction referenceable id. */ +message TransactionId { + string id = 1; +} + +/* For prepared statement handle. */ +message PreparedStatement { + uint64 handle = 1; + bool has_result_records = 2; +} + +enum AtomType { + // unspecified type. + TYPE_UNSPECIFIED = 0; + + // boolean type. + BOOLEAN = 1; + + reserved 2, 3; + + // 32-bit signed integer. + INT4 = 4; + + // 64-bit signed integer. + INT8 = 5; + + // 32-bit floating point number. + FLOAT4 = 6; + + // 64-bit floating point number. + FLOAT8 = 7; + + // multi precision decimal number. + DECIMAL = 8; + + // character sequence. + CHARACTER = 9; + + reserved 10; + + // octet sequence. + OCTET = 11; + + reserved 12; + + // bit sequence. + BIT = 13; + + reserved 14; + + // date. + DATE = 15; + + // time of day. + TIME_OF_DAY = 16; + + // time point. + TIME_POINT = 17; + + // date-time interval. + DATETIME_INTERVAL = 18; + + // time of day with time zone. + TIME_OF_DAY_WITH_TIME_ZONE = 19; + + // time point with time zone. + TIME_POINT_WITH_TIME_ZONE = 20; + + // character large objects. + CLOB = 21; + + // binary large objects. + BLOB = 22; + + reserved 23 to 30; + + // unknown type. + UNKNOWN = 31; + + reserved 32 to 99; +} + +// the user defined type. +message UserType { + // the type name. + string name = 1; +} + +// the column of relation or row type. +message Column { + // the optional column name. + string name = 1; + + // the column type. + oneof type_info { + // the atom type. + AtomType atom_type = 2; + // the row type. + RowType row_type = 3; + // the user defined type. + UserType user_type = 4; + } + reserved 5 to 10; + + // the type dimension for array types. + uint32 dimension = 11; + + // FIXME type details (e.g. decimal precisions) +} + +// the row type. +message RowType { + // the columns of the row. + repeated Column columns = 1; +} + +// pseudo type structure to represent types. +message TypeInfo { + // the column type. + oneof type_info { + // the atom type. + AtomType atom_type = 1; + // the row type. + RowType row_type = 2; + // the user defined type. + UserType user_type = 3; + } + reserved 4 to 10; + + // the type dimension for array types. + uint32 dimension = 11; +} + +// the multi precision decimal number value. +message Decimal { + // the signed unscaled value (2's complement, big endian). + bytes unscaled_value = 1; + + // the exponent of the value (value = unscaled_value * 10^exponent). + int32 exponent = 2; +} + +// the bit value. +message Bit { + // packed bit sequence (little-endian, from LST to MSB). + bytes packed = 1; + // the number of bits. + uint64 size = 2; +} + +// the time-of-day-with-time-zone value. +message TimeOfDayWithTimeZone { + // offset nano-seconds from epoch (00:00:00) in the time zone. + uint64 offset_nanoseconds = 1; + // timezone offset in minute. + sint32 time_zone_offset = 2; +} + +// the time-point value. +message TimePoint { + // offset seconds from epoch (1970-01-01 00:00:00). + sint64 offset_seconds = 1; + // nano-seconds adjustment [0, 10^9-1]. + uint32 nano_adjustment = 2; +} + +// the time-point-with-time-zone value. +message TimePointWithTimeZone { + // offset seconds from epoch (1970-01-01 00:00:00) in the time zone. + sint64 offset_seconds = 1; + // nano-seconds adjustment [0, 10^9-1]. + uint32 nano_adjustment = 2; + // timezone offset in minute. + sint32 time_zone_offset = 3; +} + +// the date-time interval value. +message DateTimeInterval { + // years offset. + sint32 year = 1; + // months offset. + sint32 month = 2; + // days offset. + sint32 day = 3; + // nano-seconds offset. + sint32 time = 4; +} + +// the character large object value. +message Clob { + + // the channel name to transfer this large object data. + string channel_name = 1; + + // the data of this large object. + oneof data { + + // absolute path of the large object data on the local file system (only for privileged mode) + string local_path = 2; + } +} + +// the binary large object value. +message Blob { + + // the channel name to transfer this large object data. + string channel_name = 1; + + // the data of this large object. + oneof data { + + // absolute path of the large object data on the local file system (only for privileged mode) + string local_path = 2; + } +} + +// Represents a kind of BLOB source location. +enum LargeObjectProvider { + + // the large object data is in BLOB reference itself. + LARGE_OBJECT_PROVIDER_UNSET = 0; + + // the large object data is persisted in the log-datastore. + DATASTORE = 1; + + // the large object data is located in SQL engine temporarily. + SQL = 2; +} + +// Represents reference to BLOB data. +message LargeObjectReference { + // The provider type who holds the large object data. + LargeObjectProvider provider = 1; + + // Object ID of the large object (unique in the provider). + uint64 object_id = 2; + + oneof contents_opt { + // large object contents for small data. + bytes contents = 3; + } +} + +// unit for time and timestamp value +enum TimeUnit { + // unit unspecified. + TIME_UNIT_UNSPECIFIED = 0; + // unit nano-second. + NANOSECOND = 1; + // unit micro-second. + MICROSECOND = 2; + // unit milli-second. + MILLISECOND = 3; +} diff --git a/mock/jogasaki/proto/sql/error.proto b/mock/jogasaki/proto/sql/error.proto new file mode 100644 index 00000000..9e5bcb01 --- /dev/null +++ b/mock/jogasaki/proto/sql/error.proto @@ -0,0 +1,195 @@ +syntax = "proto3"; + +package jogasaki.proto.sql.error; + +option java_package = "com.tsurugidb.sql.proto"; +option java_outer_classname = "SqlError"; + +/* + * Definition of error information for Response. + */ + +// the error code for sql execution +enum Code { + // code not specified + CODE_UNSPECIFIED = 0; + + // SQL-01000 generic error in SQL service + SQL_SERVICE_EXCEPTION = 1; + + // SQL-02000 generic error in SQL execution + SQL_EXECUTION_EXCEPTION = 2; + + // SQL-02001 constraint Violation + CONSTRAINT_VIOLATION_EXCEPTION = 3; + + // SQL-02002 unique constraint violation + UNIQUE_CONSTRAINT_VIOLATION_EXCEPTION = 4; + + // SQL-02003 not-null constraint violation + NOT_NULL_CONSTRAINT_VIOLATION_EXCEPTION = 5; + + // SQL-02004 referential integrity constraint violation + REFERENTIAL_INTEGRITY_CONSTRAINT_VIOLATION_EXCEPTION = 6; + + // SQL-02005 check constraint violation + CHECK_CONSTRAINT_VIOLATION_EXCEPTION = 7; + + // SQL-02010 error in expression evaluation + EVALUATION_EXCEPTION = 8; + + // SQL-02011 error in value evaluation + VALUE_EVALUATION_EXCEPTION = 9; + + // SQL-02012 non-scalar results from scalar subquery + SCALAR_SUBQUERY_EVALUATION_EXCEPTION = 10; + + // SQL-02014 SQL operation target is not found + TARGET_NOT_FOUND_EXCEPTION = 11; + + // SQL-02016 target already exists for newly creation request + TARGET_ALREADY_EXISTS_EXCEPTION = 12; + + // SQL-02018 statement is inconsistent with the request + INCONSISTENT_STATEMENT_EXCEPTION = 13; + + // SQL-02020 restricted operation was requested + RESTRICTED_OPERATION_EXCEPTION = 14; + + // SQL-02021 deletion was requested for the object with dependencies on others + DEPENDENCIES_VIOLATION_EXCEPTION = 15; + + // SQL-02022 write operation was requested using RTX + WRITE_OPERATION_BY_RTX_EXCEPTION = 16; + + // SQL-02023 LTX write operation was requested outside of write preserve + LTX_WRITE_OPERATION_WITHOUT_WRITE_PRESERVE_EXCEPTION = 17; + + // SQL-02024 read operation was requested on restricted read area + READ_OPERATION_ON_RESTRICTED_READ_AREA_EXCEPTION = 18; + + // SQL-02025 operation was requested using transaction that had already committed or aborted + INACTIVE_TRANSACTION_EXCEPTION = 19; + + // SQL-02027 error on parameters or placeholders + PARAMETER_EXCEPTION = 20; + + // SQL-02028 requested statement has unresolved placeholders + UNRESOLVED_PLACEHOLDER_EXCEPTION = 21; + + // SQL-02030 error on files for load + LOAD_FILE_EXCEPTION = 22; + + // SQL-02031 target load file is not found + LOAD_FILE_NOT_FOUND_EXCEPTION = 23; + + // SQL-02032 unexpected load file format + LOAD_FILE_FORMAT_EXCEPTION = 24; + + // SQL-02033 error on files for dump + DUMP_FILE_EXCEPTION = 25; + + // SQL-02034 dump directory is not accessible + DUMP_DIRECTORY_INACCESSIBLE_EXCEPTION = 26; + + // SQL-02036 the requested operation reached the SQL limit + SQL_LIMIT_REACHED_EXCEPTION = 27; + + // SQL-02037 the number of running transactions exceeded the maximum limit allowed, and new transaction failed to start + TRANSACTION_EXCEEDED_LIMIT_EXCEPTION = 28; + + // SQL-02039 SQL request timed out + SQL_REQUEST_TIMEOUT_EXCEPTION = 29; + + // SQL-02041 detected data corruption + DATA_CORRUPTION_EXCEPTION = 30; + + // SQL-02042 detected secondary index data corruption + SECONDARY_INDEX_CORRUPTION_EXCEPTION = 31; + + // SQL-02044 request failed before starting processing (e.g. due to pre-condition not fulfilled) + REQUEST_FAILURE_EXCEPTION = 32; + + // SQL-02045 requested transaction is not found (or already released) + TRANSACTION_NOT_FOUND_EXCEPTION = 33; + + // SQL-02046 requested statement is not found (or already released) + STATEMENT_NOT_FOUND_EXCEPTION = 34; + + // SQL-02048 detected internal error + INTERNAL_EXCEPTION = 35; + + // SQL-02050 unsupported runtime feature was requested + UNSUPPORTED_RUNTIME_FEATURE_EXCEPTION = 36; + + // SQL-02052 tried to execute operations with priority to higher priority transactions + BLOCKED_BY_HIGH_PRIORITY_TRANSACTION_EXCEPTION = 37; + + // SQL-02054 invalid value was used in runtime + INVALID_RUNTIME_VALUE_EXCEPTION = 38; + + // SQL-02056 value out of allowed range was used + VALUE_OUT_OF_RANGE_EXCEPTION = 39; + + // SQL-02058 variable length value was used exceeding the allowed maximum length + VALUE_TOO_LONG_EXCEPTION = 40; + + // SQL-02060 used value was not valid for the decimal type + INVALID_DECIMAL_VALUE_EXCEPTION = 41; + + reserved 42 to 100; + + // SQL-03000 compile error + COMPILE_EXCEPTION = 101; + + // SQL-03001 syntax error + SYNTAX_EXCEPTION = 102; + + // SQL-03002 analyze error + ANALYZE_EXCEPTION = 103; + + // SQL-03003 error on types + TYPE_ANALYZE_EXCEPTION = 104; + + // SQL-03004 error on symbols + SYMBOL_ANALYZE_EXCEPTION = 105; + + // SQL-03005 error on values + VALUE_ANALYZE_EXCEPTION = 106; + + // SQL-03010 unsupported feature/syntax was requested + UNSUPPORTED_COMPILER_FEATURE_EXCEPTION = 107; + + reserved 108 to 200; + + // SQL-04000 error in CC serialization + CC_EXCEPTION = 201; + + // SQL-04001 OCC aborted + OCC_EXCEPTION = 202; + + // SQL-04010 OCC aborted due to its read + OCC_READ_EXCEPTION = 203; + + // SQL-04015 OCC (early) aborted because it read other LTX's write preserve + CONFLICT_ON_WRITE_PRESERVE_EXCEPTION = 204; + + // SQL-04011 OCC aborted due to its write + OCC_WRITE_EXCEPTION = 205; + + // SQL-04003 LTX aborted + LTX_EXCEPTION = 206; + + // SQL-04013 LTX aborted due to its read + LTX_READ_EXCEPTION = 207; + + // SQL-04014 LTX aborted due to its write + LTX_WRITE_EXCEPTION = 208; + + // SQL-04005 RTX aborted + RTX_EXCEPTION = 209; + + // SQL-04007 request was blocked by the other operations executed concurrently + BLOCKED_BY_CONCURRENT_OPERATION_EXCEPTION = 210; + +} diff --git a/mock/jogasaki/proto/sql/request.proto b/mock/jogasaki/proto/sql/request.proto new file mode 100644 index 00000000..eb992295 --- /dev/null +++ b/mock/jogasaki/proto/sql/request.proto @@ -0,0 +1,498 @@ +syntax = "proto3"; + +package jogasaki.proto.sql.request; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.sql.proto"; +option java_outer_classname = "SqlRequest"; + +import "jogasaki/proto/sql/common.proto"; + +/* + * Definition of sub fields for Request. + */ + +// the placeholder for the prepared statements. +message Placeholder { + // the placeholder location. + oneof placement { + // the placeholder name. + string name = 2; + } + reserved 3 to 10; + + // the placeholder type. + oneof type_info { + // the atom type. + common.AtomType atom_type = 11; + // the row type. + common.RowType row_type = 12; + // the user defined type. + common.UserType user_type = 13; + } + reserved 14 to 19; + + // the type dimension for array types. + uint32 dimension = 20; +} + +// the placeholder replacements. +message Parameter { + // the placeholder location. + oneof placement { + // the placeholder name. + string name = 2; + } + reserved 3 to 10; + + // the replacement values (unset describes NULL). + oneof value { + // boolean type. + bool boolean_value = 11; + + // 32-bit signed integer. + sint32 int4_value = 14; + + // 64-bit signed integer. + sint64 int8_value = 15; + + // 32-bit floating point number. + float float4_value = 16; + + // 64-bit floating point number. + double float8_value = 17; + + // multi precision decimal number. + common.Decimal decimal_value = 18; + + // character sequence. + string character_value = 19; + + // octet sequence. + bytes octet_value = 21; + + // bit sequence. + common.Bit bit_value = 23; + + // date (number of days offset of epoch 1970-01-01). + sint64 date_value = 25; + + // time of day (nano-seconds since 00:00:00). + uint64 time_of_day_value = 26; + + // time point. + common.TimePoint time_point_value = 27; + + // date-time interval. + common.DateTimeInterval datetime_interval_value = 28; + + // time of day with time zone. + common.TimeOfDayWithTimeZone time_of_day_with_time_zone_value = 29; + + // time point with time zone. + common.TimePointWithTimeZone time_point_with_time_zone_value = 30; + + // the character large object. + common.Clob clob = 31; + + // the binary large object. + common.Blob blob = 32; + + // reference column position (for load action). + uint64 reference_column_position = 51; + + // reference column name (for load action). + string reference_column_name = 52; + } + + reserved 12, 13, 20, 22, 24, 33 to 40, 41 to 50, 53 to 99; +} + +// the parameter set for the statement. +message ParameterSet { + // a list of parameters. + repeated Parameter elements = 1; +} + +// the transaction type. +enum TransactionType { + // use default transaction type. + TRANSACTION_TYPE_UNSPECIFIED = 0; + // short transactions (optimistic concurrency control). + SHORT = 1; + // long transactions (pessimistic concurrency control). + LONG = 2; + // read only transactions (may be abort-free). + READ_ONLY = 3; +} + +// the transaction priority. +enum TransactionPriority { + // use default transaction priority. + TRANSACTION_PRIORITY_UNSPECIFIED = 0; + // halts the running transactions immediately. + INTERRUPT = 1; + // prevents new transactions and waits for the running transactions will end. + WAIT = 2; + // halts the running transactions immediately, and keep lock-out until its end. + INTERRUPT_EXCLUDE = 3; + // prevents new transactions and waits for the running transactions will end, and keep lock-out until its end. + WAIT_EXCLUDE = 4; +} + +// individual write preservation entry. +message WritePreserve { + // the target table name to preserve for writes. + string table_name = 1; +} + +// individual read area entry. +message ReadArea { + // the target table name of read area, used by inclusive_read_areas and exclusive_read_areas. + string table_name = 1; +} + +// options for beginning transactions +message TransactionOption { + // the transaction type. + TransactionType type = 1; + + // the transaction priority. + TransactionPriority priority = 2; + + // the transaction label. + string label = 3; + + // flag indicating whether the operation will change the table definition or not. + bool modifies_definitions = 4; + + reserved 5 to 10; + + // write preservations for long transactions. + repeated WritePreserve write_preserves = 11; + + // inclusive read areas for long transactions. + repeated ReadArea inclusive_read_areas = 12; + + // exclusive read areas for long transactions. + repeated ReadArea exclusive_read_areas = 13; + + reserved 14 to 20; +} + +// the transaction commit status. +enum CommitStatus { + // the default commit status (rely on the database settings). + COMMIT_STATUS_UNSPECIFIED = 0; + // commit operation has accepted, and the transaction will never abort except system errors. + ACCEPTED = 10; + // commit data has been visible for others. + AVAILABLE = 20; + // commit data has been saved on the local disk. + STORED = 30; + // commit data has been propagated to the all suitable nodes. + PROPAGATED = 40; +} + +/* + * Each request message + */ + +/* For begin request. */ +message Begin { + TransactionOption option = 1; +} + +/* For prepare request. */ +message Prepare { + string sql = 1; + repeated Placeholder placeholders = 2; +} + +/* For execute statement request. */ +message ExecuteStatement { + common.Transaction transaction_handle = 1; + string sql = 2; +} + +/* For execute query request. */ +message ExecuteQuery { + common.Transaction transaction_handle = 1; + string sql = 2; +} + +/* For execute prepared statement request. */ +message ExecutePreparedStatement { + common.Transaction transaction_handle = 1; + common.PreparedStatement prepared_statement_handle = 2; + repeated Parameter parameters = 3; +} + +/* For execute prepared query request. */ +message ExecutePreparedQuery { + common.Transaction transaction_handle = 1; + common.PreparedStatement prepared_statement_handle = 2; + repeated Parameter parameters = 3; +} + +// execute a statement with 2-D parameter table. +message Batch { + reserved 1 to 10; + + // the transaction ID. + common.Transaction transaction_handle = 11; + + // the statement ID. + common.PreparedStatement prepared_statement_handle = 12; + + // the 2-D parameter table. + repeated ParameterSet parameter_sets = 13; +} + +// Option for output files on dump failure +enum DumpFailBehavior { + // use default behavior + DUMP_FAIL_BEHAVIOR_UNSPECIFIED = 0; + + // delete output files for the failed execution + DELETE_FILES = 1; + + // keep output files even if the dump execution fails + KEEP_FILES = 2; +} + +// individual columns settings of ParquetFileFormat. +message ParquetColumnFormat { + // the target column name. + string name = 1; + + // column compression codec name (overwrites the file format setting). + string codec = 2; + + // column compression type name (overwrites the file format setting). + string encoding = 3; +} + +// dump file format for Apache Parquet. +message ParquetFileFormat { + // the parquet file format version. + string parquet_version = 1; + + // the maximum number of rows in the same row group. + int64 record_batch_size = 2; + + // the approximately maximum row group size in bytes. + int64 record_batch_in_bytes = 3; + + // common compression codec name of the individual columns. + string codec = 4; + + // common encoding type of the individual columns. + string encoding = 5; + + reserved 6 to 10; + + // settings of each column. + repeated ParquetColumnFormat columns = 11; +} + +// CHAR column metadata type for Arrow files. +enum ArrowCharacterFieldType { + // use default metadata type for CHAR columns. + ARROW_CHARACTER_FIELD_TYPE_UNSPECIFIED = 0; + + // use StringBuilder for CHAR columns. + STRING = 1; + + // use FixedSizeBinaryBuilder for CHAR columns. + FIXED_SIZE_BINARY = 2; +} + +// dump file format for Apache Arrow. +message ArrowFileFormat { + // the metadata format version. + string metadata_version = 1; + + // the byte alignment of each values. + int32 alignment = 2; + + // the maximum number of records in record batch. + int64 record_batch_size = 3; + + // the approximately maximum size of each record batch in bytes. + int64 record_batch_in_bytes = 4; + + // compression codec name. + string codec = 5; + + // threshold for adopting compressed data. + double min_space_saving = 6; + + // CHAR column metadata type. + ArrowCharacterFieldType character_field_type = 7; +} + +// options for dump request +message DumpOption { + reserved 1 to 10; + + // Behavior on failure + DumpFailBehavior fail_behavior = 11; + + // record count limit for dump output file + uint64 max_record_count_per_file = 12; + + // time unit used for timestamp columns + common.TimeUnit timestamp_unit = 13; + + reserved 14 to 20; + + // dump output file format specification. + oneof file_format { + + // dump tables as Apache Parquet format. + ParquetFileFormat parquet = 21; + + // dump tables as Apache Arrow format. + ArrowFileFormat arrow = 22; + + } +} + +/* For execute dump request. */ +message ExecuteDump { + common.Transaction transaction_handle = 1; + common.PreparedStatement prepared_statement_handle = 2; + repeated Parameter parameters = 3; + string directory = 4; + DumpOption option = 5; +} + +/* For execute load request. */ +message ExecuteLoad { + // optional transaction handle (empty for non-transactional load). + oneof transaction_handle_opt { + common.Transaction transaction_handle = 1; + } + common.PreparedStatement prepared_statement_handle = 2; + repeated Parameter parameters = 3; + repeated string file = 4; +} + +/* For commit request. */ +message Commit { + common.Transaction transaction_handle = 1; + + // response will be returned after reaching the commit status. + CommitStatus notification_type = 2; + + // dispose the target transaction handle only if notifies a successfully commit to the client. + bool auto_dispose = 3; +} + +/* For rollback request. */ +message Rollback { + common.Transaction transaction_handle = 1; +} + +/* For dispose prepared sql. */ +message DisposePreparedStatement { + common.PreparedStatement prepared_statement_handle = 1; +} + +/* For explain text sql. */ +message ExplainByText { + string sql = 1; +} + +/* For explain prepared sql. */ +message Explain { + common.PreparedStatement prepared_statement_handle = 1; + repeated Parameter parameters = 2; +} + +// describe about the table. +message DescribeTable { + reserved 1 to 10; + + // the table name to describe. + string name = 11; +} + +// describe available table names in the database. +message ListTables { + reserved 1 to 10; +} + +// describe the current search path. +message GetSearchPath { + reserved 1 to 10; +} + +// retrieves error information of the transaction. +message GetErrorInfo { + // the target transaction handle. + common.Transaction transaction_handle = 1; +} + +// occurred when the client side transaction handle is disposed. +message DisposeTransaction { + // the target transaction handle. + common.Transaction transaction_handle = 1; +} + +// Requests to extract executing statement info in payload data. +message ExtractStatementInfo { + + // session ID + uint64 session_id = 1; + + // payload data as plain byte array. + bytes payload = 2; +} + +// request to retrieve large object data. +message GetLargeObjectData { + + // the reference to large object data to retrieve. + common.LargeObjectReference reference = 1; + +} + +/* For request message to the SQL service. */ +message Request { + common.Session session_handle = 1; + oneof request { + Begin begin = 2; + Prepare prepare = 3; + ExecuteStatement execute_statement = 4; + ExecuteQuery execute_query = 5; + ExecutePreparedStatement execute_prepared_statement = 6; + ExecutePreparedQuery execute_prepared_query = 7; + Commit commit = 8; + Rollback rollback = 9; + DisposePreparedStatement dispose_prepared_statement = 10; + // Disconnect disconnect = 11; + Explain explain = 12; + ExecuteDump execute_dump = 13; + ExecuteLoad execute_load = 14; + DescribeTable describe_table = 15; + Batch batch = 16; + ListTables listTables = 17; + GetSearchPath getSearchPath = 18; + GetErrorInfo get_error_info = 19; + DisposeTransaction dispose_transaction = 20; + ExplainByText explain_by_text = 21; + ExtractStatementInfo extract_statement_info = 22; + GetLargeObjectData get_large_object_data = 23; + } + + reserved 24 to 99; + + // service message version (major) + uint64 service_message_version_major = 100; + + // service message version (minor) + uint64 service_message_version_minor = 101; + +} diff --git a/mock/jogasaki/proto/sql/response.proto b/mock/jogasaki/proto/sql/response.proto new file mode 100644 index 00000000..24a5502b --- /dev/null +++ b/mock/jogasaki/proto/sql/response.proto @@ -0,0 +1,352 @@ +syntax = "proto3"; + +package jogasaki.proto.sql.response; + +option java_multiple_files = false; +option java_package = "com.tsurugidb.sql.proto"; +option java_outer_classname = "SqlResponse"; + +import "jogasaki/proto/sql/common.proto"; +import "jogasaki/proto/sql/error.proto"; + +/* + * Definition of sub fields for Response. + */ + +/* For response of success when there is no data to return. */ +message Success { +} + +/* For response of error containing a error message. */ +message Error { + + reserved 1; + + // error message text + string detail = 2; + + // error code + error.Code code = 3; + + // supplemental text for debug purpose + string supplemental_text = 4; +} + + +/* + * Each response message + */ + +/* For response to ExecuteQuery, ExecutePreparedQuery, Commit, Rollback, +DisposePreparedStatement, DisposeTransaction and ExecuteDump. */ +message ResultOnly { + oneof result { + Success success = 1; + Error error = 2; + } +} + +/* For response to Begin. */ +message Begin { + + // request is successfully completed. + message Success { + + // the transaction handle. + common.Transaction transaction_handle = 1; + + // the transaction id for reference. + common.TransactionId transaction_id = 2; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 1; + + // engine error occurred. + Error error = 2; + } +} + +/* For response to Prepare. */ +message Prepare { + oneof result { + common.PreparedStatement prepared_statement_handle = 1; + Error error = 2; + } +} + +/* For response to ExecuteQuery and/or ExecutePreparedQuery. */ +message ExecuteQuery { + string name = 1; /* The name of the channel to which the ResultSet set will be sent. */ + ResultSetMetadata record_meta = 2; +} + +/* For response to Explain. */ +message Explain { + + // request is successfully completed. + message Success { + + // the content format ID. + string format_id = 1; + + // the content format version. + uint64 format_version = 2; + + // the explain result contents. + string contents = 3; + + // the result set column information, or empty if it does not provided. + repeated common.Column columns = 4; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + Error error = 2; + } +} + +// describe about a table. +message DescribeTable { + reserved 1 to 10; + + // request is successfully completed. + message Success { + + // the database name. + string database_name = 1; + + // the schema name. + string schema_name = 2; + + // the table name. + string table_name = 3; + + // the table column information. + repeated common.Column columns = 4; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // engine error was occurred. + Error error = 12; + } +} + +message Identifier { + // the label. + string label = 1; +} + +message Name { + // the identfiers. + repeated Identifier identifiers = 1; +} + +// execute a list tables. +message ListTables { + reserved 2 to 10; + + // request is successfully completed. + message Success { + // the table path names. + repeated Name table_path_names = 1; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // engine error was occurred. + Error error = 12; + } +} + +// execute a getSearchPath. +message GetSearchPath { + reserved 2 to 10; + // request is successfully completed. + + message Success { + // the search path. + repeated Name search_paths = 1; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // engine error was occurred. + Error error = 12; + } +} + +// empty message. +message Void {} + +// retrieves error information of the transaction. +message GetErrorInfo { + // the response body. + oneof result { + // operation was successfully completed and error information was found. + Error success = 1; + + // operation was successfully completed but error information was absent. + Void error_not_found = 2; + + // engine error occurred while the retrieving existing error information. + Error error = 3; + } +} + +// execute a DisposeTransaction. +message DisposeTransaction { + // the response body. + oneof result { + // operation was successfully completed and error information was found. + Void success = 1; + + // engine error occurred while the retrieving existing error information. + Error error = 2; + } +} + +/* For response to ExecuteStatement, ExecutePreparedStatement and ExecuteLoad. */ +message ExecuteResult { + reserved 1 to 10; + + // request is successfully completed. + message Success { + + // group of counters during SQL execution. + repeated CounterEntry counters = 1; + } + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // engine error was occurred. + Error error = 12; + } + + // a counted item. + message CounterEntry { + // the counter type. + CounterType type = 1; + + // the count. + int64 value = 2; + } + + // a kind of execution counter. + enum CounterType { + // the un-categorized counter type. + COUNTER_TYPE_UNSPECIFIED = 0; + + // The number of rows inserted in the execution. + INSERTED_ROWS = 10; + + // The number of rows updated in the execution. + UPDATED_ROWS = 20; + + // The number of rows merged in the execution. + MERGED_ROWS = 30; + + // The number of rows deleted in the execution. + DELETED_ROWS = 40; + } +} + +// response of GetLargeObjectData request. +message GetLargeObjectData { + + // request is successfully completed. + message Success { + + // the data channel name of retrieved large object data. + string channel_name = 1; + + } + + reserved 1 to 10; + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // error was occurred. + Error error = 12; + } +} + +/* For response message from the SQL service. */ +message Response { + oneof response { + ResultOnly result_only = 1; + Begin begin = 2; + Prepare prepare = 3; + ExecuteQuery execute_query = 4; + Explain explain = 5; + DescribeTable describe_table = 6; + // 7 is no longer used. + ListTables list_tables = 8; + GetSearchPath get_search_path = 9; + GetErrorInfo get_error_info = 10; + DisposeTransaction dispose_transaction = 11; + ExecuteResult execute_result = 12; + ExtractStatementInfo extract_statement_info = 13; + GetLargeObjectData get_large_object_data = 14; + } +} + +// metadata of result sets. +message ResultSetMetadata { + + // the column information. + repeated common.Column columns = 1; +} + +// Response of ExtractStatementInfo. +message ExtractStatementInfo { + + // request is successfully completed. + message Success { + + // the transaction ID for the statement. + oneof transaction_id_opt { + // the corresponding transaction ID. + common.TransactionId transaction_id = 1; + } + + // the executing SQL text of the statement. + oneof sql_opt { + // SQL statement string. + string sql = 2; + } + } + + reserved 1 to 10; + + // the response body. + oneof result { + // request is successfully completed. + Success success = 11; + + // error was occurred. + Error error = 12; + } + +} diff --git a/mock/jogasaki/serializer/base128v.cpp b/mock/jogasaki/serializer/base128v.cpp new file mode 100644 index 00000000..3f38f137 --- /dev/null +++ b/mock/jogasaki/serializer/base128v.cpp @@ -0,0 +1,167 @@ +#include "base128v.h" + +#include + +namespace jogasaki::serializer::base128v { + +using takatori::util::buffer_view; + +bool write_unsigned( + std::uint64_t value, + buffer_view::iterator& iterator, + buffer_view::const_iterator end) noexcept { + using value_type = buffer_view::value_type; + auto value_work = value; + auto iter_work = iterator; // NOLINT(readability-qualified-auto) + for (std::uint64_t i = 0; i < 8; ++i) { + if (iter_work == end) { + return false; + } + + // the 1st ~ 8th groups have continue bit. + // cvvv vvvv + // c - continue bit + // v - 7-bit value block + auto group_value = value_work & 0x7fULL; + + // shift out the current group + value_work >>= 7ULL; + + // check the rest bits + if (value_work == 0) { + // no more value bits + *iter_work = static_cast(group_value); + ++iter_work; + iterator = iter_work; + return true; + } + + // more value bits are rest + *iter_work = static_cast(0x80ULL | group_value); + ++iter_work; + } + if (iter_work == end) { + return false; + } + + // the 9th group has no continue bit. + // vvvv vvvv + // v - 8-bit value block + *iter_work = static_cast(value_work & 0xffULL); + ++iter_work; + iterator = iter_work; + return true; +} + +template +static std::optional impl_read_unsigned( + Iter& iterator, + buffer_view::const_iterator end) noexcept { + std::uint64_t result {}; + auto iter_work = iterator; + for (std::uint64_t i = 0; i < 8; ++i) { + if (iter_work == end) { + return std::nullopt; + } + auto group = static_cast(static_cast(*iter_work)); + if (group == 0 && i != 0) { + // for strict, all zeros group is not allowed, except just represents 0 + return std::nullopt; + } + ++iter_work; + + // the 1st ~ 8th groups have continue bit. + // cvvv vvvv + // c - continue bit + // v - 7-bit value block + result |= (group & 0x7fULL) << (i * 7); + if ((group & 0x80ULL) == 0) { + // end of sequence + result |= (group & 0x7fULL) << (i * 7); + iterator = iter_work; + return { result }; + } + // more groups are rest + } + if (iter_work == end) { + return std::nullopt; + } + + auto group = static_cast(static_cast(*iter_work)); + if (group == 0) { + // for strict, all zeros group is not allowed + return std::nullopt; + } + ++iter_work; + + // the 9th group has no continue bit. + // vvvv vvvv + // v - 8-bit value block + result |= (group & 0xffULL) << 56ULL; + iterator = iter_work; + return { result }; +} + +std::optional read_unsigned( + buffer_view::iterator& iterator, + buffer_view::const_iterator end) noexcept { + return impl_read_unsigned(iterator, end); +} + +std::optional read_unsigned( + buffer_view::const_iterator& iterator, + buffer_view::const_iterator end) noexcept { + return impl_read_unsigned(iterator, end); +} + +static std::uint64_t encode_signed(std::int64_t value) { + std::uint64_t work {}; + std::memcpy(&work, &value, sizeof(value)); + work <<= 1ULL; + if (value < 0) { + work = ~work; + } + return work; +} + +static std::int64_t decode_signed(std::uint64_t encoded) { + std::uint64_t work = encoded; + work >>= 1ULL; + if ((encoded & 0x01ULL) != 0) { + work = ~work; + } + std::int64_t result {}; + std::memcpy(&result, &work, sizeof(work)); + return result; +} + +[[nodiscard]] size_type size_signed(std::int64_t value) { + return size_unsigned(encode_signed(value)); +} + +bool write_signed( + std::int64_t value, + buffer_view::iterator& iterator, + buffer_view::const_iterator end) { + return write_unsigned(encode_signed(value), iterator, end); +} + +std::optional read_signed( + buffer_view::iterator& iterator, + buffer_view::const_iterator end) { + if (auto result = impl_read_unsigned(iterator, end)) { + return { decode_signed(*result) }; + } + return std::nullopt; +} + +std::optional read_signed( + buffer_view::const_iterator& iterator, + buffer_view::const_iterator end) { + if (auto result = impl_read_unsigned(iterator, end)) { + return { decode_signed(*result) }; + } + return std::nullopt; +} + +} // namespace jogasaki::serializer::base128v diff --git a/mock/jogasaki/serializer/base128v.h b/mock/jogasaki/serializer/base128v.h new file mode 100644 index 00000000..d49ce293 --- /dev/null +++ b/mock/jogasaki/serializer/base128v.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#include + +/** + * @brief serialize/deserialize integers using base128 variant. + * @details + * base128 variant is differ from the original base128 as following: + * + * * each groups ordered as little-endian + * * the 9-th group allocates 8-bits + */ +namespace jogasaki::serializer::base128v { + +using size_type = std::size_t; +using takatori::util::buffer_view; + +/** + * @brief compute the base128v encoded size. + * @param value the target value + * @return the encoded size in bytes + */ +[[nodiscard]] constexpr size_type size_unsigned(std::uint64_t value) noexcept { + for (std::uint64_t i = 1; i <= 8; ++i) { + if (value < (1ULL << (i * 7))) { + return i; + } + } + return 9; +} + +/** + * @brief writes the value as base128 variant into the buffer. + * @details + * This advances the `iterator` argument only if the operation was succeeded. + * If remaining buffer is not enough, this will do nothing and return `false`. + * @param value the value to write + * @param iterator [INOUT] the buffer iterator + * @param end the buffer limit + * @return true if the encoded value is successfully written to the buffer + * @return false if remaining buffer is not enough + */ +bool write_unsigned( + std::uint64_t value, + buffer_view::iterator& iterator, + buffer_view::const_iterator end) noexcept; + +/** + * @brief reads a value encoded by base128 variant from the buffer. + * @details + * This advances the `iterator` argument only if the operation was succeeded. + * If the encoded value is wrong, this will do nothing and return an `empty` value. + * @param iterator [INOUT] the buffer iterator + * @param end the buffer limit + * @return an encoded value if this successfully extracted the value from the buffer + * @return empty if the encoded value is wrong + */ +std::optional read_unsigned( + buffer_view::iterator& iterator, + buffer_view::const_iterator end) noexcept; + +/// @copydoc read_unsigned() +std::optional read_unsigned( + buffer_view::const_iterator& iterator, + buffer_view::const_iterator end) noexcept; + +/// @copydoc size_unsigned() +[[nodiscard]] size_type size_signed(std::int64_t value); + +/// @copydoc write_unsigned() +bool write_signed( + std::int64_t value, + buffer_view::iterator& iterator, + buffer_view::const_iterator end); + +/// @copydoc read_unsigned() +std::optional read_signed( + buffer_view::iterator& iterator, + buffer_view::const_iterator end); + +/// @copydoc read_unsigned() +std::optional read_signed( + buffer_view::const_iterator& iterator, + buffer_view::const_iterator end); + +} // namespace jogasaki::serializer::base128v diff --git a/mock/jogasaki/serializer/details/value_io_constants.h b/mock/jogasaki/serializer/details/value_io_constants.h new file mode 100644 index 00000000..76233d55 --- /dev/null +++ b/mock/jogasaki/serializer/details/value_io_constants.h @@ -0,0 +1,127 @@ +#pragma once + +#include + +#include + +namespace jogasaki::serializer::details { + +static constexpr std::uint32_t header_embed_positive_int = 0x00U; + +static constexpr std::uint32_t header_embed_character = 0x40U; + +static constexpr std::uint32_t header_embed_row = 0x80U; + +static constexpr std::uint32_t header_embed_array = 0xa0U; + +static constexpr std::uint32_t header_embed_negative_int = 0xc0U; + +static constexpr std::uint32_t header_embed_octet = 0xd0U; + +static constexpr std::uint32_t header_embed_bit = 0xe0U; + +static constexpr std::uint32_t header_unknown = 0xe8U; + +static constexpr std::uint32_t header_int = 0xe9U; + +static constexpr std::uint32_t header_float4 = 0xeaU; + +static constexpr std::uint32_t header_float8 = 0xebU; + +static constexpr std::uint32_t header_decimal_compact = 0xecU; + +static constexpr std::uint32_t header_decimal = 0xedU; + +static constexpr std::uint32_t header_time_of_day_with_offset = 0xeeU; + +static constexpr std::uint32_t header_time_point_with_offset = 0xefU; + +static constexpr std::uint32_t header_character = 0xf0U; + +static constexpr std::uint32_t header_octet = 0xf1U; + +static constexpr std::uint32_t header_bit = 0xf2U; + +static constexpr std::uint32_t header_date = 0xf3U; + +static constexpr std::uint32_t header_time_of_day = 0xf4U; + +static constexpr std::uint32_t header_time_point = 0xf5U; + +static constexpr std::uint32_t header_datetime_interval = 0xf6U; + +static constexpr std::uint32_t header_reserved_f7 = 0xf7U; + +static constexpr std::uint32_t header_row = 0xf8U; + +static constexpr std::uint32_t header_array = 0xf9U; + +static constexpr std::uint32_t header_clob = 0xfaU; + +static constexpr std::uint32_t header_blob = 0xfbU; + +static constexpr std::uint32_t header_reserved_fc = 0xfcU; + +static constexpr std::uint32_t header_reserved_fd = 0xfdU; + +static constexpr std::uint32_t header_end_of_contents = 0xfeU; + +static constexpr std::uint32_t header_reserved_ff = 0xffU; + + +static constexpr std::uint32_t mask_embed_positive_int = 0x3fU; + +static constexpr std::uint32_t mask_embed_character = 0x3fU; + +static constexpr std::uint32_t mask_embed_row = 0x1fU; + +static constexpr std::uint32_t mask_embed_array = 0x1fU; + +static constexpr std::uint32_t mask_embed_negative_int = 0x0fU; + +static constexpr std::uint32_t mask_embed_octet = 0x0fU; + +static constexpr std::uint32_t mask_embed_bit = 0x07U; + + +static constexpr std::int32_t min_embed_positive_int_value = 0x00; + +static constexpr std::int32_t max_embed_positive_int_value = mask_embed_positive_int + min_embed_positive_int_value; + + +static constexpr std::int32_t max_embed_negative_int_value = -1; + +static constexpr std::int32_t min_embed_negative_int_value = + max_embed_negative_int_value - static_cast(mask_embed_negative_int); + + +static constexpr std::uint32_t min_embed_character_size = 0x01; + +static constexpr std::uint32_t max_embed_character_size = mask_embed_character + min_embed_character_size; + + +static constexpr std::uint32_t min_embed_octet_size = 0x01; + +static constexpr std::uint32_t max_embed_octet_size = mask_embed_octet + min_embed_octet_size; + + +static constexpr std::uint32_t min_embed_bit_size = 0x01; + +static constexpr std::uint32_t max_embed_bit_size = mask_embed_bit + min_embed_bit_size; + + +static constexpr std::uint32_t min_embed_row_size = 0x01; + +static constexpr std::uint32_t max_embed_row_size = mask_embed_row + min_embed_row_size; + + +static constexpr std::uint32_t min_embed_array_size = 0x01; + +static constexpr std::uint32_t max_embed_array_size = mask_embed_array + min_embed_array_size; + +static constexpr std::uint32_t limit_size = static_cast(std::numeric_limits::max()) + 1UL; + + +static constexpr std::uint64_t max_decimal_coefficient_size = sizeof(std::uint64_t) * 2 + 1; + +} // namespace jogasaki::serializer::details diff --git a/mock/jogasaki/serializer/entry_type.h b/mock/jogasaki/serializer/entry_type.h new file mode 100644 index 00000000..14e12712 --- /dev/null +++ b/mock/jogasaki/serializer/entry_type.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include + +#include +#include + +namespace jogasaki::serializer { + +/// @brief represents a kind of serialized entry. +enum class entry_type { + + /// @brief represents end of contents. + end_of_contents, + + /// @brief represents value is absent. + null, + + /** + * @brief 64-bit signed integer. + * @note this represents {@code int4}, {@code int8} or {@code boolean}. + */ + int_, + + /// @brief fixed 32-bit floating point number. + float4, + + /// @brief fixed 64-bit floating point number. + float8, + + /// @brief decimal number. + decimal, + + /// @brief variable length character sequence. + character, + + /// @brief variable length octet sequence. + octet, + + /// @brief variable length bit sequence. + bit, + + /// @brief date value. + date, + + /// @brief time of day value. + time_of_day, + + /// @brief time of day with offset value. + time_of_day_with_offset, + + /// @brief time-point value. + time_point, + + /// @brief time-point with offset value. + time_point_with_offset, + + /// @brief date-time interval value. + datetime_interval, + + /// @brief rows. + row, + + /// @brief arrays. + array, + + /// @brief character large objects. + clob, + + /// @brief binary large objects. + blob, +}; + +/** + * @brief returns string representation of the value. + * @param value the target value + * @return the corresponded string representation + */ +constexpr inline std::string_view to_string_view(entry_type value) noexcept { + using namespace std::string_view_literals; + using kind = entry_type; + switch (value) { + case kind::end_of_contents: return "end_of_contents"sv; + case kind::null: return "null"sv; + case kind::int_: return "int_"sv; + case kind::float4: return "float4"sv; + case kind::float8: return "float8"sv; + case kind::decimal: return "decimal"sv; + case kind::character: return "character"sv; + case kind::octet: return "octet"sv; + case kind::bit: return "bit"sv; + case kind::date: return "date"sv; + case kind::time_of_day: return "time_of_day"sv; + case kind::time_of_day_with_offset: return "time_of_day_with_offset"sv; + case kind::time_point: return "time_point"sv; + case kind::time_point_with_offset: return "time_point_with_offset"sv; + case kind::datetime_interval: return "datetime_interval"sv; + case kind::row: return "row"sv; + case kind::array: return "array"sv; + case kind::clob: return "clob"sv; + case kind::blob: return "blob"sv; + } + std::abort(); +} + +/** + * @brief appends string representation of the given value. + * @param out the target output + * @param value the target value + * @return the output + */ +inline std::ostream& operator<<(std::ostream& out, entry_type value) { + return out << to_string_view(value); +} + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/namespace.h b/mock/jogasaki/serializer/namespace.h new file mode 100644 index 00000000..232c0fdc --- /dev/null +++ b/mock/jogasaki/serializer/namespace.h @@ -0,0 +1,6 @@ +#pragma once + +/** + * @brief serializes SQL models. + */ +namespace jogasaki::serializer {} diff --git a/mock/jogasaki/serializer/value_input.cpp b/mock/jogasaki/serializer/value_input.cpp new file mode 100644 index 00000000..0c65733a --- /dev/null +++ b/mock/jogasaki/serializer/value_input.cpp @@ -0,0 +1,536 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "base128v.h" +#include "details/value_io_constants.h" + +namespace jogasaki::serializer { + +using namespace details; + +using takatori::util::buffer_view; +using takatori::util::const_buffer_view; +using takatori::util::const_bitset_view; + +using byte_type = buffer_view::value_type; + +static void requires_entry( + entry_type expect, + buffer_view::const_iterator position, + buffer_view::const_iterator end) { + if (auto ret = peek_type(position, end); ret != expect) { + using ::takatori::util::string_builder; + using ::takatori::util::throw_exception; + throw_exception(std::runtime_error(string_builder {} + << "inconsistent entry type: " + << "retrieved '" << ret << "', " + << "but expected is '" << expect << "'" + << string_builder::to_string)); + } +} + +template +static std::optional extract( + buffer_view::value_type first, + std::uint32_t header, + std::uint32_t mask, + T min_value) { + auto unsigned_value = static_cast(first); + if (header <= unsigned_value && unsigned_value <= header + mask) { + return { static_cast(unsigned_value - header) + min_value }; + } + return std::nullopt; +} + +static std::int64_t read_sint(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + if (auto result = base128v::read_signed(position, end)) { + return *result; + } + throw_buffer_underflow(); +} + +static std::uint64_t read_uint(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + if (auto result = base128v::read_unsigned(position, end)) { + return *result; + } + throw_buffer_underflow(); +} + +static std::int32_t read_sint32(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + auto value = read_sint(position, end); + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw_int32_value_out_of_range(value); + } + return static_cast(value); +} + +static std::uint32_t read_size(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + auto size = read_uint(position, end); + if (size >= limit_size) { + throw_size_out_of_range(size, limit_size); + } + return static_cast(size); +} + +static const_buffer_view read_bytes( + std::size_t size, + buffer_view::const_iterator& position, + buffer_view::const_iterator end) { + if (std::distance(position, end) < static_cast(size)) { + throw_buffer_underflow(); + } + const_buffer_view result { position, size }; + position += size; + return result; +} + +template +static T read_fixed(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + static_assert(std::is_integral_v); + static_assert(std::is_unsigned_v); + if (std::distance(position, end) < static_cast(sizeof(T))) { + throw_buffer_underflow(); + } + T result { 0 }; + for (std::size_t i = 1; i <= sizeof(T); ++i) { + T value { static_cast(*position) }; + result |= value << ((sizeof(T) - i) * 8U); + ++position; + } + return result; +} + +void read_end_of_contents(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::end_of_contents, position, end); + ++position; +} + +void read_null(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::null, position, end); + ++position; +} + +entry_type peek_type(buffer_view::const_iterator position, buffer_view::const_iterator end) { + if (position == end) { + throw_buffer_underflow(); + } + std::uint32_t head = static_cast(*position); + if (head <= header_embed_positive_int + mask_embed_positive_int) { + return entry_type::int_; + } + static_assert(header_embed_positive_int + mask_embed_positive_int + 1 == header_embed_character); + if (head <= header_embed_character + mask_embed_character) { + return entry_type::character; + } + static_assert(header_embed_character + mask_embed_character + 1 == header_embed_row); + if (head <= header_embed_row + mask_embed_row) { + return entry_type::row; + } + static_assert(header_embed_row + mask_embed_row + 1 == header_embed_array); + if (head <= header_embed_array + mask_embed_array) { + return entry_type::array; + } + static_assert(header_embed_array + mask_embed_array + 1 == header_embed_negative_int); + if (head <= header_embed_negative_int + mask_embed_negative_int) { + return entry_type::int_; + } + static_assert(header_embed_negative_int + mask_embed_negative_int + 1 == header_embed_octet); + if (head <= header_embed_octet + mask_embed_octet) { + return entry_type::octet; + } + static_assert(header_embed_bit + mask_embed_bit + 1 == header_unknown); + if (head <= header_embed_bit + mask_embed_bit) { + return entry_type::bit; + } + switch (head) { + case header_int: return entry_type::int_; + case header_float4: return entry_type::float4; + case header_float8: return entry_type::float8; + case header_decimal_compact: return entry_type::decimal; + case header_decimal: return entry_type::decimal; + case header_character: return entry_type::character; + case header_octet: return entry_type::octet; + case header_bit: return entry_type::bit; + case header_date: return entry_type::date; + case header_time_of_day: return entry_type::time_of_day; + case header_time_of_day_with_offset: return entry_type::time_of_day_with_offset; + case header_time_point: return entry_type::time_point; + case header_time_point_with_offset: return entry_type::time_point_with_offset; + case header_datetime_interval: return entry_type::datetime_interval; + case header_row: return entry_type::row; + case header_array: return entry_type::array; + case header_clob: return entry_type::clob; + case header_blob: return entry_type::blob; + case header_end_of_contents: return entry_type::end_of_contents; + case header_unknown: return entry_type::null; + + default: + throw_unrecognized_entry(static_cast(head)); + } +} + +std::int64_t read_int(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::int_, position, end); + auto first = *position; + if (auto value = extract( + first, + header_embed_positive_int, + mask_embed_positive_int, + min_embed_positive_int_value)) { + ++position; + return *value; + } + if (auto value = extract( + first, + header_embed_negative_int, + mask_embed_negative_int, + min_embed_negative_int_value)) { + ++position; + return *value; + } + + BOOST_ASSERT(static_cast(first) == header_int); // NOLINT + buffer_view::const_iterator iter = position; + ++iter; + auto result = read_sint(iter, end); + position = iter; + return result; +} + +float read_float4(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::float4, position, end); + buffer_view::const_iterator iter = position; + ++iter; + + auto bits = read_fixed(iter, end); + float result {}; + std::memcpy(&result, &bits, sizeof(result)); + position = iter; + return result; +} + +double read_float8(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::float8, position, end); + buffer_view::const_iterator iter = position; + ++iter; + + auto bits = read_fixed(iter, end); + double result {}; + std::memcpy(&result, &bits, sizeof(result)); + position = iter; + return result; +} + +const_buffer_view read_decimal_coefficient(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + auto size = read_uint(position, end); + if (size == 0 || size > max_decimal_coefficient_size) { + throw_decimal_coefficient_out_of_range(size); + } + auto bytes = read_bytes(size, position, end); + if (size != max_decimal_coefficient_size) { + return bytes; + } + + auto first = static_cast(bytes[0]); + // positive is OK because coefficient is [0, 2^128) + if (first == 0) { + return bytes; + } + + if (first == 0xffU) { + // check negative value to avoid -2^128 (0xff 0x00.. 0x00) + auto const* found = std::find_if( + bytes.begin() + 1, + bytes.end(), + [](auto c) { return c != '\0'; }); + if (found != bytes.end()) { + return bytes; + } + } + + throw_decimal_coefficient_out_of_range(size); +} + +takatori::decimal::triple read_decimal(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + // int encoded + if (peek_type(position, end) == entry_type::int_) { + auto value = read_int(position, end); + return takatori::decimal::triple { value, 0 }; + } + + // decimal encoded + requires_entry(entry_type::decimal, position, end); + + buffer_view::const_iterator iter = position; + auto first = static_cast(*iter); + ++iter; + + // compact decimal value + if (first == header_decimal_compact) { + auto exponent = read_sint32(iter, end); + auto coefficient = read_sint(iter, end); + position = iter; + return { coefficient, exponent }; + } + + // full decimal value + BOOST_ASSERT(first == header_decimal); // NOLINT + + auto exponent = read_sint32(iter, end); + auto coefficient = read_decimal_coefficient(iter, end); + + // extract lower 8-octets of coefficient + std::uint64_t c_lo {}; + std::uint64_t shift {}; + for ( + std::size_t offset = 0; + offset < coefficient.size() && offset < sizeof(std::uint64_t); + ++offset) { + auto pos = coefficient.size() - offset - 1; + std::uint64_t octet { static_cast(coefficient[pos]) }; + c_lo |= octet << shift; + shift += 8; + } + + // extract upper 8-octets of coefficient + std::uint64_t c_hi {}; + shift = 0; + for ( + std::size_t offset = sizeof(std::uint64_t); + offset < coefficient.size() && offset < sizeof(std::uint64_t) * 2; + ++offset) { + auto pos = coefficient.size() - offset - 1; + std::uint64_t octet { static_cast(coefficient[pos]) }; + c_hi |= octet << shift; + shift += 8; + } + + bool negative = (static_cast(coefficient[0]) & 0x80U) != 0; + + if (negative) { + // sign extension + if (coefficient.size() < sizeof(std::uint64_t) * 2) { + BOOST_ASSERT(coefficient.size() >= sizeof(std::uint64_t)); // NOLINT + auto mask = std::numeric_limits::max(); // 0xfff..... + std::size_t rest = (coefficient.size() - sizeof(std::uint64_t)) * 8U; + c_hi |= mask << rest; + } + + c_lo = ~c_lo + 1; + c_hi = ~c_hi; + if (c_lo == 0) { + c_hi += 1; // carry up + } + // if negative, coefficient must not be zero + BOOST_ASSERT(c_lo != 0 || c_hi != 0); // NOLINT + } + + position = iter; + return takatori::decimal::triple { + negative ? -1 : +1, + c_hi, + c_lo, + exponent, + }; +} + +std::string_view read_character(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::character, position, end); + buffer_view::const_iterator iter = position; + std::size_t size {}; + auto first = *iter; + if (auto value = extract( + first, + header_embed_character, + mask_embed_character, + min_embed_character_size)) { + size = *value; + ++iter; + } else { + BOOST_ASSERT(static_cast(first) == header_character); // NOLINT + ++iter; + size = read_size(iter, end); + } + + auto result = read_bytes(size, iter, end); + position = iter; + return { result.data(), result.size() }; +} + +std::string_view read_octet(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::octet, position, end); + buffer_view::const_iterator iter = position; + std::size_t size {}; + auto first = *iter; + if (auto value = extract( + first, + header_embed_octet, + mask_embed_octet, + min_embed_octet_size)) { + size = *value; + ++iter; + } else { + BOOST_ASSERT(static_cast(first) == header_octet); // NOLINT + ++iter; + size = read_size(iter, end); + } + + auto result = read_bytes(size, iter, end); + position = iter; + return { result.data(), result.size() }; +} + +const_bitset_view read_bit(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::bit, position, end); + buffer_view::const_iterator iter = position; + std::size_t size {}; + auto first = *iter; + if (auto value = extract( + first, + header_embed_bit, + mask_embed_bit, + min_embed_bit_size)) { + size = *value; + ++iter; + } else { + BOOST_ASSERT(static_cast(first) == header_bit); // NOLINT + ++iter; + size = read_size(iter, end); + } + + std::size_t block_size = (size + 7) / 8; + auto result = read_bytes(block_size, iter, end); + position = iter; + return { result.data(), size }; +} + +takatori::datetime::date read_date(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::date, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto offset = read_sint(iter, end); + position = iter; + return takatori::datetime::date { offset }; +} + +takatori::datetime::time_of_day read_time_of_day(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::time_of_day, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto offset = read_uint(iter, end); + position = iter; + return takatori::datetime::time_of_day { takatori::datetime::time_of_day::time_unit { offset }}; +} + +std::pair read_time_of_day_with_offset(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::time_of_day_with_offset, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto offset = read_uint(iter, end); + auto timezone_offset = read_sint32(iter, end); + position = iter; + return {takatori::datetime::time_of_day { takatori::datetime::time_of_day::time_unit { offset }}, timezone_offset}; +} + +takatori::datetime::time_point read_time_point(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::time_point, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto offset = read_sint(iter, end); + auto adjustment = read_uint(iter, end); + position = iter; + return takatori::datetime::time_point { + takatori::datetime::time_point::offset_type { offset }, + takatori::datetime::time_point::subsecond_unit { adjustment }, + }; +} + +std::pair read_time_point_with_offset(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::time_point_with_offset, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto offset = read_sint(iter, end); + auto adjustment = read_uint(iter, end); + auto timezone_offset = read_sint32(iter, end); + position = iter; + return { + takatori::datetime::time_point { + takatori::datetime::time_point::offset_type { offset }, + takatori::datetime::time_point::subsecond_unit { adjustment }, + }, + timezone_offset + }; +} + +takatori::datetime::datetime_interval read_datetime_interval(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::datetime_interval, position, end); + buffer_view::const_iterator iter = position; + ++iter; + auto year = read_sint32(iter, end); + auto month = read_sint32(iter, end); + auto day = read_sint32(iter, end); + auto time = read_sint(iter, end); + position = iter; + return takatori::datetime::datetime_interval { + { year, month, day }, + takatori::datetime::time_interval { takatori::datetime::time_interval::time_unit { time } }, + }; +} + +std::size_t read_array_begin(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::array, position, end); + auto first = *position; + if (auto size = extract( + first, + header_embed_array, + mask_embed_array, + min_embed_array_size)) { + ++position; + return *size; + } + + BOOST_ASSERT(static_cast(first) == header_array); // NOLINT + buffer_view::const_iterator iter = position; + ++iter; + auto size = read_size(iter, end); + position = iter; + return size; +} + +std::size_t read_row_begin(buffer_view::const_iterator& position, buffer_view::const_iterator end) { + requires_entry(entry_type::row, position, end); + auto first = *position; + if (auto size = extract( + first, + header_embed_row, + mask_embed_row, + min_embed_row_size)) { + ++position; + return *size; + } + + BOOST_ASSERT(static_cast(first) == header_row); // NOLINT + buffer_view::const_iterator iter = position; + ++iter; + auto size = read_size(iter, end); + position = iter; + return size; +} + +// FIXME: impl blob, clob + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_input.h b/mock/jogasaki/serializer/value_input.h new file mode 100644 index 00000000..5bcc3773 --- /dev/null +++ b/mock/jogasaki/serializer/value_input.h @@ -0,0 +1,251 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "entry_type.h" +#include "value_input_exception.h" + +namespace jogasaki::serializer { + +using takatori::util::buffer_view; +using takatori::util::bitset_view; +using takatori::util::const_bitset_view; + +/** + * @brief returns the entry type of the iterator position. + * @details this operation does not advance the buffer iterator. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the current entry type + * @return entry_type::buffer_underflow if there are no more entries in this buffer + * @throw value_input_exception if the buffer is empty + * @throw value_input_exception if the input entry type is not supported + */ +entry_type peek_type(buffer_view::const_iterator position, buffer_view::const_iterator end); + +/** + * @brief just consumes `end_of_contents` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @throws std::runtime_error if the entry is not expected type + * @see peek_type() + */ +void read_end_of_contents(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief just consumes `null` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @throws std::runtime_error if the entry is not expected type + * @see peek_type() + */ +void read_null(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `int` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::int64_t read_int(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief peaks `float4` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +float read_float4(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `float8` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +double read_float8(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `decimal` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note This also recognizes `int` entries because `decimal` values sometimes encoded as `int` value. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +takatori::decimal::triple read_decimal(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `character` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned std::string_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::string_view read_character(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `octet` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned std::string_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::string_view read_octet(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `bit` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned bitset_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +const_bitset_view read_bit(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `date` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +takatori::datetime::date read_date(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `time_of_day` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +takatori::datetime::time_of_day read_time_of_day(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `time_of_day` with offset entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value, the second element contains timezone offset + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::pair read_time_of_day_with_offset(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `time_point` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +takatori::datetime::time_point read_time_point(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `time_point` with offset entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value, the second element contains timezone offset + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::pair read_time_point_with_offset(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `datetime_interval` entry on the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the retrieved value + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +takatori::datetime::datetime_interval read_datetime_interval(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `array` entry on the current position. + * @details This entry does not its elements: + * please retrieve individual values using `read_xxx()` function for each the returned element count. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the consequent number of elements in the array + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::size_t read_array_begin(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +/** + * @brief retrieves `row` entry on the current position. + * @details This entry does not its elements: + * please retrieve individual values using `read_xxx()` function for each the returned element count. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return the consequent number of elements in the row + * @throws std::runtime_error if the entry is not expected type + * @throws value_input_exception if the encoded value is not valid + * @see peek_type() + */ +std::size_t read_row_begin(buffer_view::const_iterator& position, buffer_view::const_iterator end); + +// FIXME: impl blob, clob + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_input_exception.cpp b/mock/jogasaki/serializer/value_input_exception.cpp new file mode 100644 index 00000000..3d6ba9f9 --- /dev/null +++ b/mock/jogasaki/serializer/value_input_exception.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#include +#include + +#include + +#include "details/value_io_constants.h" + +namespace jogasaki::serializer { + +using namespace details; + +using ::takatori::util::string_builder; +using ::takatori::util::throw_exception; + +value_input_exception::value_input_exception(value_input_exception::reason_code reason, char const* message) : + std::runtime_error { message }, + reason_ { reason } +{} + +value_input_exception::value_input_exception(value_input_exception::reason_code reason, std::string const& message) : + std::runtime_error { message }, + reason_ { reason } +{} + +value_input_exception::reason_code value_input_exception::reason() const noexcept { + return reason_; +} + +std::ostream& operator<<(std::ostream& out, value_input_exception::reason_code value) { + return out << to_string_view(value); +} + + +void throw_buffer_underflow() { + throw_exception(value_input_exception { + value_input_exception::reason_code::buffer_underflow, + "input buffer is underflow", + }); +} + +void throw_unrecognized_entry(std::uint32_t header) { + throw_exception(value_input_exception { + value_input_exception::reason_code::unrecognized_entry_type, + string_builder {} + << "unrecognized entry type: " << header + << string_builder::to_string, + }); +} + +void throw_unsupported_entry(std::uint32_t header) { + throw_exception(value_input_exception { + value_input_exception::reason_code::unsupported_entry_type, + string_builder {} + << "unsupported entry type: " << header + << string_builder::to_string, + }); +} + +void throw_int32_value_out_of_range(std::int64_t value) { + throw_exception(value_input_exception { + value_input_exception::reason_code::value_out_of_range, + string_builder {} + << "value out of range: " << value << ", " + << "must be in [" << std::numeric_limits::min() << ", " + << std::numeric_limits::max() << "]" + << string_builder::to_string, + }); +} + +void throw_decimal_coefficient_out_of_range(std::size_t nbytes) { + throw_exception(value_input_exception { + value_input_exception::reason_code::value_out_of_range, + string_builder {} + << "decimal value out of range: coefficient bytes=" << nbytes << ", " + << "must be <= " << (max_decimal_coefficient_size - 1) << ", " + << "or = " << max_decimal_coefficient_size << " and the first byte is 0x00 or 0xff" + << string_builder::to_string, + }); +} + +void throw_size_out_of_range(std::uint64_t size, std::uint64_t limit) { + throw_exception(value_input_exception { + value_input_exception::reason_code::value_out_of_range, + string_builder {} + << "too large size: " << size << ", " + << "must be less than" << limit + << string_builder::to_string, + }); +} + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_input_exception.h b/mock/jogasaki/serializer/value_input_exception.h new file mode 100644 index 00000000..30d8d66d --- /dev/null +++ b/mock/jogasaki/serializer/value_input_exception.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace jogasaki::serializer { + +/** + * @brief an exception occurs when the value_input reached broken input. + */ +class value_input_exception : public std::runtime_error { +public: + /** + * @brief the reason code of individual erroneous situations. + */ + enum class reason_code { + /// @brief reached end of buffer before reading value is completed. + buffer_underflow, + + /// @brief unrecognized entry type. + unrecognized_entry_type, + + /// @brief unsupported entry type. + unsupported_entry_type, + + /// @brief value is out of range. + value_out_of_range, + }; + + /** + * @brief creates a new instance. + * @param reason the reason code + * @param message the error message + */ + explicit value_input_exception(reason_code reason, char const* message); + + /** + * @brief creates a new instance. + * @param reason the reason code + * @param message the error message + */ + explicit value_input_exception(reason_code reason, std::string const& message); + + /** + * @brief returns the reason code of this exception. + * @return the reason code + */ + [[nodiscard]] reason_code reason() const noexcept; + +private: + reason_code reason_; +}; + +/** + * @brief raise a new exception for buffer underflow. + */ +[[noreturn]] void throw_buffer_underflow(); + +/** + * @brief raise a new exception for unrecognized entry kind. + * @param header the header byte + */ +[[noreturn]] void throw_unrecognized_entry(std::uint32_t header); + +/** + * @brief raise a new exception for unsupported entry kind. + * @param header the header byte + */ +[[noreturn]] void throw_unsupported_entry(std::uint32_t header); + +/** + * @brief raise a new exception for extracted 32-bit signed int value is out of range. + * @param value the extracted value + */ +[[noreturn]] void throw_int32_value_out_of_range(std::int64_t value); + +/** + * @brief raise a new exception for extracted decimal value is out of range. + * @param nbytes the number of octets + */ +[[noreturn]] void throw_decimal_coefficient_out_of_range(std::size_t nbytes); + +/** + * @brief raise a new exception for extracted size is out of range. + * @param size the extracted size + * @param limit the size limit + */ +[[noreturn]] void throw_size_out_of_range(std::uint64_t size, std::uint64_t limit); + +/** + * @brief returns string representation of the value. + * @param value the target value + * @return the corresponded string representation + */ +constexpr std::string_view to_string_view(value_input_exception::reason_code value) noexcept { + using namespace std::string_view_literals; + using kind = value_input_exception::reason_code; + switch (value) { + case kind::buffer_underflow: return "buffer_underflow"sv; + case kind::unrecognized_entry_type: return "unrecognized_entry_type"sv; + case kind::unsupported_entry_type: return "unsupported_entry_type"sv; + case kind::value_out_of_range: return "value_out_of_range"sv; + } + std::abort(); +} + +/** + * @brief appends string representation of the given value. + * @param out the target output + * @param value the target value + * @return the output + */ +std::ostream& operator<<(std::ostream& out, value_input_exception::reason_code value); + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_output.cpp b/mock/jogasaki/serializer/value_output.cpp new file mode 100644 index 00000000..d27c4b1c --- /dev/null +++ b/mock/jogasaki/serializer/value_output.cpp @@ -0,0 +1,554 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "base128v.h" +#include "details/value_io_constants.h" + +namespace jogasaki::serializer { + +using namespace details; + +using takatori::util::buffer_view; +using takatori::util::const_bitset_view; +using takatori::util::throw_exception; +using takatori::util::buffer_view; + +using byte_type = buffer_view::value_type; + +[[nodiscard]] static std::size_t buffer_remaining( + buffer_view::const_iterator position, + buffer_view::const_iterator end) { + auto diff = std::distance(position, end); + if (diff < 0) { + return 0; + } + return static_cast(diff); +} + +static void write_fixed8( + std::uint64_t value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + BOOST_ASSERT(position < end); // NOLINT + (void) end; + *position = static_cast(value); + ++position; +} + +template +static void write_fixed( + T value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + for (std::uint64_t i = 1; i <= sizeof(T); ++i) { + write_fixed8(value >> ((sizeof(T) - i) * 8U), position, end); + } +} + +static void write_bytes( + byte_type const* data, + std::size_t count, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + BOOST_ASSERT(position + count <= end); // NOLINT + (void) end; + std::memcpy(position, data, count); + position += count; +} + +bool write_end_of_contents( + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8(header_end_of_contents, position, end); + return true; +} + +bool write_null( + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8(header_unknown, position, end); + return true; +} + +bool write_int( + std::int64_t value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (min_embed_positive_int_value <= value && value <= max_embed_positive_int_value) { + // embed positive int + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8( + static_cast(header_embed_positive_int) + value - min_embed_positive_int_value, + position, + end); + } else if (min_embed_negative_int_value <= value && value <= max_embed_negative_int_value) { + // embed negative int + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8( + static_cast(header_embed_negative_int) + value - min_embed_negative_int_value, + position, + end); + } else { + // normal int + if (buffer_remaining(position, end) < 1 + base128v::size_signed(value)) { + return false; + } + + write_fixed8(header_int, position, end); + base128v::write_signed(value, position, end); + } + return true; +} + +bool write_float4( + float value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + 4) { + return false; + } + std::uint32_t bits {}; + std::memcpy(&bits, &value, sizeof(bits)); + + write_fixed8(header_float4, position, end); + write_fixed(bits, position, end); + return true; +} + +bool write_float8( + double value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + 8) { + return false; + } + std::uint64_t bits {}; + std::memcpy(&bits, &value, sizeof(bits)); + + write_fixed8(header_float8, position, end); + write_fixed(bits, position, end); + return true; +} + +static bool has_small_coefficient(takatori::decimal::triple value) { + if (value.coefficient_high() != 0) { + return false; + } + if (value.coefficient_low() <= static_cast(std::numeric_limits::max())) { + return true; + } + if (value.coefficient_low() == static_cast(std::numeric_limits::min()) + && value.sign() < 0) { + return true; + } + return false; +} + +static std::tuple make_signed_coefficient_full(takatori::decimal::triple value) { + BOOST_ASSERT(!has_small_coefficient(value)); // NOLINT + std::uint64_t c_hi = value.coefficient_high(); + std::uint64_t c_lo = value.coefficient_low(); + + if (value.sign() >= 0) { + for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { + std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU; + if (octet != 0) { + std::size_t size { sizeof(std::uint64_t) * 2 - offset }; + if ((octet & 0x80U) != 0) { + ++size; + } + return { c_hi, c_lo, size }; + } + } + return { c_hi, c_lo, sizeof(std::uint64_t) + 1 }; + } + + // for negative numbers + + if (value.sign() < 0) { + c_lo = ~c_lo + 1; + c_hi = ~c_hi; + if (c_lo == 0) { + c_hi += 1; // carry up + } + } + + for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { + std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU; + if (octet != 0xffU) { + std::size_t size { sizeof(std::uint64_t) * 2 - offset }; + if ((octet & 0x80U) == 0) { + ++size; + } + return { c_hi, c_lo, size }; + } + } + return { c_hi, c_lo, sizeof(std::uint64_t) + 1 }; +} + +bool write_decimal( + takatori::decimal::triple value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + // small coefficient + if (has_small_coefficient(value)) { + auto coefficient = static_cast(value.coefficient_low()); + if (value.sign() < 0) { + coefficient = -coefficient; + } + + // just write as int if exponent is just 0 + if (value.exponent() == 0) { + return write_int(coefficient, position, end); + } + + // write compact decimal + if (buffer_remaining(position, end) < 1 + + base128v::size_signed(value.exponent()) + + base128v::size_signed(coefficient)) { + return false; + } + write_fixed8(header_decimal_compact, position, end); + base128v::write_signed(value.exponent(), position, end); + base128v::write_signed(coefficient, position, end); + return true; + } + + // for large coefficient + auto [c_hi, c_lo, c_size] = make_signed_coefficient_full(value); + BOOST_ASSERT(c_size > sizeof(std::uint64_t)); // NOLINT + BOOST_ASSERT(c_size <= sizeof(std::uint64_t) * 2 + 1); // NOLINT + + if (buffer_remaining(position, end) < 1 + + base128v::size_signed(value.exponent()) + + base128v::size_unsigned(c_size) + + c_size) { + return false; + } + + write_fixed8(header_decimal, position, end); + base128v::write_signed(value.exponent(), position, end); + base128v::write_unsigned(c_size, position, end); + + if (c_size > sizeof(std::uint64_t) * 2) { + // write sign bit + if (value.sign() >= 0) { + write_fixed8(0, position, end); + } else { + write_fixed8(0xffU, position, end); + } + --c_size; + } + + // write octets of coefficient + for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { + *(position + c_size - offset - 1) = static_cast(c_lo >> (offset * 8U)); + } + for (std::size_t offset = 0; offset < c_size - sizeof(std::uint64_t); ++offset) { + *(position + c_size - offset - sizeof(std::uint64_t) - 1) = static_cast(c_hi >> (offset * 8U)); + } + position += c_size; + return true; +} + +bool write_character( + std::string_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + auto size = value.size(); + + if (min_embed_character_size <= size && size <= max_embed_character_size) { + // for short character string + if (buffer_remaining(position, end) < 1 + size) { + return false; + } + write_fixed8( + static_cast(header_embed_character) + size - min_embed_character_size, + position, + end); + } else { + // for long character string + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(size) + size) { + return false; + } + write_fixed8(header_character, position, end); + base128v::write_unsigned(size, position, end); + } + write_bytes(value.data(), value.size(), position, end); + return true; +} + +bool write_octet( + std::string_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + auto size = value.size(); + + if (min_embed_octet_size <= size && size <= max_embed_octet_size) { + // for short octet string + if (buffer_remaining(position, end) < 1 + size) { + return false; + } + write_fixed8( + static_cast(header_embed_octet) + size - min_embed_octet_size, + position, + end); + } else { + // for long octet string + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(size) + size) { + return false; + } + write_fixed8(header_octet, position, end); + base128v::write_unsigned(size, position, end); + } + write_bytes(value.data(), value.size(), position, end); + return true; +} + +bool write_bit( + const_bitset_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + auto bit_size = value.size(); + auto byte_size = value.block_size(); + + if (min_embed_bit_size <= bit_size && bit_size <= max_embed_bit_size) { + // for short bit string + if (buffer_remaining(position, end) < 1 + byte_size) { + return false; + } + write_fixed8( + static_cast(header_embed_bit) + bit_size - min_embed_bit_size, + position, + end); + } else { + // for long bit string + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(bit_size) + byte_size) { + return false; + } + write_fixed8(header_bit, position, end); + base128v::write_unsigned(bit_size, position, end); + } + auto rest_bits = value.size() % 8; + if (rest_bits == 0) { + // write all blocks + write_bytes(value.block_data(), value.block_size(), position, end); + } else { + // write blocks except the last + write_bytes(value.block_data(), value.block_size() - 1, position, end); + + auto last = static_cast(*--value.end()); + write_fixed8(last & ~(0xffU << rest_bits), position, end); + } + return true; +} + +bool write_bit( + std::string_view blocks, + std::size_t number_of_bits, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (number_of_bits > blocks.size() * 8) { + throw_exception(std::out_of_range("too large number of bits")); + } + const_bitset_view bits { blocks.data(), number_of_bits }; + BOOST_ASSERT(bits.block_size() <= blocks.size()); // NOLINT + return write_bit(bits, position, end); +} + +bool write_date( + takatori::datetime::date value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + base128v::size_signed(value.days_since_epoch())) { + return false; + } + write_fixed8(header_date, position, end); + base128v::write_signed(value.days_since_epoch(), position, end); + return true; +} + +bool write_time_of_day( + takatori::datetime::time_of_day value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(value.time_since_epoch().count())) { + return false; + } + write_fixed8(header_time_of_day, position, end); + base128v::write_unsigned(value.time_since_epoch().count(), position, end); + return true; +} + +bool write_time_of_day_with_offset( + takatori::datetime::time_of_day value, + std::int32_t timezone_offset, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(value.time_since_epoch().count()) + + base128v::size_signed(timezone_offset)) { + return false; + } + write_fixed8(header_time_of_day_with_offset, position, end); + base128v::write_unsigned(value.time_since_epoch().count(), position, end); + base128v::write_signed(timezone_offset, position, end); + return true; +} + +bool write_time_point( + takatori::datetime::time_point value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + + base128v::size_signed(value.seconds_since_epoch().count()) + + base128v::size_unsigned(value.subsecond().count())) { + return false; + } + write_fixed8(header_time_point, position, end); + base128v::write_signed(value.seconds_since_epoch().count(), position, end); + base128v::write_unsigned(value.subsecond().count(), position, end); + return true; +} + +bool write_time_point_with_offset( + takatori::datetime::time_point value, + std::int32_t timezone_offset, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + + base128v::size_signed(value.seconds_since_epoch().count()) + + base128v::size_unsigned(value.subsecond().count()) + + base128v::size_signed(timezone_offset)) { + return false; + } + write_fixed8(header_time_point_with_offset, position, end); + base128v::write_signed(value.seconds_since_epoch().count(), position, end); + base128v::write_unsigned(value.subsecond().count(), position, end); + base128v::write_signed(timezone_offset, position, end); + return true; +} + +bool write_datetime_interval( + takatori::datetime::datetime_interval value, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + + base128v::size_signed(value.date().year()) + + base128v::size_signed(value.date().month()) + + base128v::size_signed(value.date().day()) + + base128v::size_signed(value.time().offset().count())) { + return false; + } + write_fixed8(header_datetime_interval, position, end); + base128v::write_signed(value.date().year(), position, end); + base128v::write_signed(value.date().month(), position, end); + base128v::write_signed(value.date().day(), position, end); + base128v::write_signed(value.time().offset().count(), position, end); + return true; +} + +bool write_array_begin( + std::size_t size, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (min_embed_array_size <= size && size <= max_embed_array_size) { + // for short array + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8( + static_cast(header_embed_array) + size - min_embed_array_size, + position, + end); + } else { + // for long array + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(size)) { + return false; + } + write_fixed8(header_array, position, end); + base128v::write_unsigned(size, position, end); + } + return true; +} + +bool write_row_begin( + std::size_t size, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (min_embed_row_size <= size && size <= max_embed_row_size) { + // for short row + if (buffer_remaining(position, end) < 1) { + return false; + } + write_fixed8( + static_cast(header_embed_row) + size - min_embed_row_size, + position, + end); + } else { + // for long row + if (buffer_remaining(position, end) < 1 + base128v::size_unsigned(size)) { + return false; + } + write_fixed8(header_row, position, end); + base128v::write_unsigned(size, position, end); + } + return true; +} + +bool write_blob( + std::uint64_t provider, + std::uint64_t object_id, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + + sizeof(std::uint64_t) + + sizeof(std::uint64_t)) { + return false; + } + write_fixed8(header_blob, position, end); + write_fixed(provider, position, end); + write_fixed(object_id, position, end); + return true; +} + +bool write_clob( + std::uint64_t provider, + std::uint64_t object_id, + buffer_view::iterator& position, + buffer_view::const_iterator end) { + if (buffer_remaining(position, end) < 1 + + sizeof(std::uint64_t) + + sizeof(std::uint64_t)) { + return false; + } + write_fixed8(header_clob, position, end); + write_fixed(provider, position, end); + write_fixed(object_id, position, end); + return true; +} + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_output.h b/mock/jogasaki/serializer/value_output.h new file mode 100644 index 00000000..c53a864e --- /dev/null +++ b/mock/jogasaki/serializer/value_output.h @@ -0,0 +1,325 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace jogasaki::serializer { + +using takatori::util::buffer_view; +using takatori::util::bitset_view; +using takatori::util::const_bitset_view; + +/** + * @brief puts `end_of_contents` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_end_of_contents( + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `null` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_null( + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `int` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_int( + std::int64_t value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `float4` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_float4( + float value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `float8` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_float8( + double value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `decimal` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note This may write as `int` entry if th value is in the range of 64-bit signed integer. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_decimal( + takatori::decimal::triple value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `character` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned std::string_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_character( + std::string_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `octet` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned std::string_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_octet( + std::string_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `bit` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned bitset_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_bit( + const_bitset_view value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `bit` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @note The returned bitset_view refers onto the input buffer. + * Please escape the returned value before the buffer will be disposed. + * @param blocks the bit blocks + * @param number_of_bits the number of bits to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + * @throws std::out_of_range if `number_of_bits` is too large for `blocks` + */ +bool write_bit( + std::string_view blocks, + std::size_t number_of_bits, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `date` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_date( + takatori::datetime::date value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `time_of_day` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_time_of_day( + takatori::datetime::time_of_day value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `time_of_day` with timezone offset entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param timezone_offset the timezone offset (in minute) to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_time_of_day_with_offset( + takatori::datetime::time_of_day value, + std::int32_t timezone_offset, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `time_point` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_time_point( + takatori::datetime::time_point value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `time_point` with timezone offset entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param timezone_offset the timezone offset (in minute) to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_time_point_with_offset( + takatori::datetime::time_point value, + std::int32_t timezone_offset, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `datetime_interval` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param value the value to write + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_datetime_interval( + takatori::datetime::datetime_interval value, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `array` entry onto the current position. + * @details This entry does not its elements: + * please retrieve individual values using `write_xxx()` function for each the returned element count. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param size the number of elements in the array, must be less than `2^31` for interoperability + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + * @throws std::out_of_range if size is out of range + */ +bool write_array_begin( + std::size_t size, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `row` entry onto the current position. + * @details This entry does not its elements: + * please retrieve individual values using `write_xxx()` function for each the returned element count. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param size the number of elements in the row, must be less than `2^31` for interoperability + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + * @throws std::out_of_range if size is out of range + */ +bool write_row_begin( + std::size_t size, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `blob` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param provider the provider of the blob, see jogasaki::proto::sql::common::LargeObjectProvider + * @param object_id the id of the blob object + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_blob( + std::uint64_t provider, + std::uint64_t object_id, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +/** + * @brief puts `clob` entry onto the current position. + * @details This operation will advance the buffer iterator to the next entry, only if it is successfully completed. + * @param provider the provider of the clob, see jogasaki::proto::sql::common::LargeObjectProvider + * @param object_id the id of the clob object + * @param position the buffer content iterator + * @param end the buffer ending position + * @return true the operation successfully completed + * @return false the remaining buffer is too short to write contents + */ +bool write_clob( + std::uint64_t provider, + std::uint64_t object_id, + buffer_view::iterator& position, + buffer_view::const_iterator end); + +} // namespace jogasaki::serializer diff --git a/mock/jogasaki/serializer/value_writer.h b/mock/jogasaki/serializer/value_writer.h new file mode 100644 index 00000000..44b3d12f --- /dev/null +++ b/mock/jogasaki/serializer/value_writer.h @@ -0,0 +1,354 @@ +#pragma once + +#include + +#include + +#include "value_output.h" + +#include + +namespace jogasaki::serializer { + +using takatori::util::buffer_view; + +/** + * @brief writes value entries into a backing writer. + * @tparam Writer the backed writer type, should declare `T::write(char const* data, size_type size) -> result_type` + * @tparam Size the size type represents the number of bytes to write + */ +template +class value_writer { +public: + /// @brief the backed writer type. + using writer_type = Writer; + + /// @brief Size the size type represents the number of bytes to write. + using size_type = Size; + + /// @brief the result type of `T::write(char const*, size_type)`. + using result_type = decltype( + std::declval().write( + std::declval(), + std::declval())); + + /** + * @brief creates a new instance. + * @param writer the destination writer + */ + explicit value_writer(writer_type& writer) noexcept : + writer_ { std::addressof(writer) } + {} + + /** + * @brief puts `end_of_contents` entry onto the current position. + */ + result_type write_end_of_contents() { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_end_of_contents(iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `null` entry onto the current position. + */ + result_type write_null() { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_null(iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `int` entry onto the current position. + */ + result_type write_int(std::int64_t value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_int(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `float4` entry onto the current position. + * @param value the value to write + */ + result_type write_float4(float value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_float4(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `float8` entry onto the current position. + * @param value the value to write + */ + result_type write_float8(double value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_float8(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `decimal` entry onto the current position. + * @param value the value to write + */ + result_type write_decimal(takatori::decimal::triple value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_decimal(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `character` entry onto the current position. + * @param value the value to write + */ + result_type write_character(std::string_view value) { + auto buf = buffer(value.size() + 10); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_character(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `octet` entry onto the current position. + * @param value the value to write + */ + result_type write_octet(std::string_view value) { + auto buf = buffer(value.size() + 10); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_octet(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `bit` entry onto the current position. + * @param value the value to write + */ + result_type write_bit(const_bitset_view value) { + auto buf = buffer(value.block_size() + 10); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_bit(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `bit` entry onto the current position. + * @param blocks the bit blocks + * @param number_of_bits the number of bits to write + */ + result_type write_bit(std::string_view blocks, std::size_t number_of_bits) { + auto buf = buffer(blocks.size() + 16); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_bit(blocks, number_of_bits, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `date` entry onto the current position. + * @param value the value to write + */ + result_type write_date(takatori::datetime::date value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_date(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `time_of_day` entry onto the current position. + * @param value the value to write + */ + result_type write_time_of_day(takatori::datetime::time_of_day value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_time_of_day(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `time_of_day` with offset entry onto the current position. + * @param value the value to write + * @param timezone_offset the timezone offset in minute + */ + result_type write_time_of_day_with_offset(takatori::datetime::time_of_day value, std::int32_t timezone_offset) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_time_of_day_with_offset(value, timezone_offset, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `time_point` entry onto the current position. + * @param value the value to write + */ + result_type write_time_point(takatori::datetime::time_point value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_time_point(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `time_point` with offset entry onto the current position. + * @param value the value to write + * @param timezone_offset the timezone offset in minute + */ + result_type write_time_point_with_offset(takatori::datetime::time_point value, std::int32_t timezone_offset) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_time_point_with_offset(value, timezone_offset, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `datetime_interval` entry onto the current position. + * @param value the value to write + */ + result_type write_datetime_interval(takatori::datetime::datetime_interval value) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_datetime_interval(value, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `array` entry onto the current position. + * @param size the number of elements in the array, must be less than `2^31` for interoperability + */ + result_type write_array_begin(std::size_t size) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_array_begin(size, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `row` entry onto the current position. + * @param size the number of elements in the row, must be less than `2^31` for interoperability + */ + result_type write_row_begin(std::size_t size) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_row_begin(size, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `blob` entry onto the current position. + * @param provider the provider of the blob, see jogasaki::proto::sql::common::LargeObjectProvider + * @param object_id the id of the blob object + */ + result_type write_blob(std::uint64_t provider, std::uint64_t object_id) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_blob(provider, object_id, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + + /** + * @brief puts `clob` entry onto the current position. + * @param provider the provider of the clob, see jogasaki::proto::sql::common::LargeObjectProvider + * @param object_id the id of the clob object + */ + result_type write_clob(std::uint64_t provider, std::uint64_t object_id) { + auto buf = buffer(); + auto *iter = buf.begin(); + auto ret = ::jogasaki::serializer::write_clob(provider, object_id, iter, buf.end()); + BOOST_ASSERT(ret); // NOLINT + (void) ret; + + auto write_size = static_cast(std::distance(buf.begin(), iter)); + return writer_->write(buf.data(), write_size); + } + +private: + std::vector buffer_; + writer_type* writer_; + + [[nodiscard]] buffer_view buffer(std::size_t reserve = 1024) { + if (buffer_.size() < reserve) { + buffer_.resize(reserve); + } + return buffer_view { buffer_.data(), buffer_.size() }; + } +}; + +} // namespace jogasaki::serializer diff --git a/mock/tateyama/server/glog_helper.h b/mock/tateyama/server/glog_helper.h new file mode 100644 index 00000000..f543a357 --- /dev/null +++ b/mock/tateyama/server/glog_helper.h @@ -0,0 +1,98 @@ +/* + * Copyright 2019-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +namespace tateyama::server { + +// intended to be included from backend.cpp only once +static void setup_glog(tateyama::api::configuration::whole* conf) { + auto* glog_section = conf->get_section("glog"); + + // logtostderr + if (auto logtostderr_env = getenv("GLOG_logtostderr"); logtostderr_env) { + FLAGS_logtostderr = true; + } else { + auto logtostderr = glog_section->get("logtostderr"); + if (logtostderr) { + FLAGS_logtostderr = logtostderr.value(); + } + } + + // stderrthreshold + if (auto stderrthreshold_env = getenv("GLOG_stderrthreshold"); stderrthreshold_env) { + FLAGS_stderrthreshold = static_cast<::google::int32>(strtol(stderrthreshold_env, nullptr, 10)); + } else { + auto stderrthreshold = glog_section->get("stderrthreshold"); + if (stderrthreshold) { + FLAGS_stderrthreshold = stderrthreshold.value(); + } + } + + // minloglevel + if (auto minloglevel_env = getenv("GLOG_minloglevel"); minloglevel_env) { + FLAGS_minloglevel = static_cast<::google::int32>(strtol(minloglevel_env, nullptr, 10)); + } else { + auto minloglevel = glog_section->get("minloglevel"); + if (minloglevel) { + FLAGS_minloglevel = minloglevel.value(); + } + } + + // log_dir + if (auto log_dir_env = getenv("GLOG_log_dir"); log_dir_env) { + FLAGS_log_dir=log_dir_env; + } else { + auto log_dir = glog_section->get("log_dir"); + if (log_dir) { + FLAGS_log_dir=log_dir.value().string(); + } + } + + // max_log_size + if (auto max_log_size_env = getenv("GLOG_max_log_size"); max_log_size_env) { + FLAGS_max_log_size = static_cast<::google::int32>(strtol(max_log_size_env, nullptr, 10)); + } else { + auto max_log_size = glog_section->get("max_log_size"); + if (max_log_size) { + FLAGS_max_log_size = max_log_size.value(); + } + } + + // v + if (auto v_env = getenv("GLOG_v"); v_env) { + FLAGS_v = static_cast<::google::int32>(strtol(v_env, nullptr, 10)); + } else { + auto v = glog_section->get("v"); + if (v) { + FLAGS_v = v.value(); + } + } + + // logbuflevel + if (auto logbuflevel_env = getenv("GLOG_logbuflevel"); logbuflevel_env) { + FLAGS_logbuflevel = static_cast<::google::int32>(strtol(logbuflevel_env, nullptr, 10)); + } else { + auto logbuflevel = glog_section->get("logbuflevel"); + if (logbuflevel) { + FLAGS_logbuflevel = logbuflevel.value(); + } + } + + google::InitGoogleLogging("mock_server"); + google::InstallFailureSignalHandler(); +} + +} // tateyama::server diff --git a/mock/tateyama/server/mock_server.cpp b/mock/tateyama/server/mock_server.cpp new file mode 100644 index 00000000..48f8be96 --- /dev/null +++ b/mock/tateyama/server/mock_server.cpp @@ -0,0 +1,247 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "glog_helper.h" +#include "mock_server.h" + +namespace tateyama::server { + +static constexpr std::string_view default_configuration_for_mock_server { // NOLINT + "[sql]\n" + "thread_pool_size=5\n" + "enable_index_join=false\n" + "stealing_enabled=true\n" + "default_partitions=5\n" + "stealing_wait=1\n" + "task_polling_wait=0\n" + "lightweight_job_level=0\n" + "enable_hybrid_scheduler=true\n" + + "[ipc_endpoint]\n" + "database_name=tsurugi\n" + "threads=104\n" + "datachannel_buffer_size=64\n" + "max_datachannel_buffers=16\n" + "admin_sessions=1\n" + + "[stream_endpoint]\n" + "port=12345\n" + "threads=104\n" + + "[session]\n" + "enable_timeout=false\n" + "refresh_timeout=300\n" + "max_refresh_timeout=86400\n" + + "[fdw]\n" + "name=tsurugi\n" + "threads=104\n" + + "[datastore]\n" + "log_location=\n" + "logging_max_parallelism=112\n" + + "[cc]\n" + "epoch_duration=40000\n" + + "[system]\n" + "pid_directory = /tmp\n" + + "[glog]\n" + "dummy=\n" // just for retain glog section in default configuration +}; + +static bool shutdown_requested{}; + +static void signal_handler([[maybe_unused]] int sig) { + std::cout << std::endl << "catch " << strsignal(sig) << " signal" << std::endl; + shutdown_requested = true; +} + +int +mock_server_main() { + if (signal(SIGINT, signal_handler) == SIG_ERR) { // NOLINT #define SIG_ERR ((__sighandler_t) -1) in a system header file + std::cerr << "cannot register signal handler" << std::endl; + return 1; + } + if (signal(SIGQUIT, signal_handler) == SIG_ERR) { // NOLINT #define SIG_ERR ((__sighandler_t) -1) in a system header file + std::cerr << "cannot register signal handler" << std::endl; + return 1; + } + + // configuration + std::stringstream ss{}; + ss << "[ipc_endpoint]\n"; + ss << "database_name=mock\n"; + ss << "[glog]\n"; + ss << "logtostderr=false\n"; + auto conf = std::make_shared(ss, default_configuration_for_mock_server); + setup_glog(conf.get()); + + tateyama::server::mock_server tgsv{framework::boot_mode::database_server, conf}; + tateyama::server::add_core_components(tgsv); + + // status_info + auto status_info = tgsv.find_resource(); + + if (!tgsv.setup()) { + status_info->whole(tateyama::status_info::state::boot_error); + return 1; + } + + status_info->whole(tateyama::status_info::state::ready); + + if (!tgsv.start()) { + status_info->whole(tateyama::status_info::state::boot_error); + // detailed message must have been logged in the components where start error occurs + LOG(ERROR) << "Starting server failed due to errors in starting server application framework."; + tgsv.shutdown(); + return 1; + } + + status_info->whole(tateyama::status_info::state::activated); + + // wait for a shutdown request + while(!shutdown_requested) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // termination process + status_info->whole(tateyama::status_info::state::deactivating); + tgsv.shutdown(); + + return 0; +} + + +void mock_server::add_resource(std::shared_ptr arg) { + environment_->resource_repository().add(std::move(arg)); +} + +void mock_server::add_service(std::shared_ptr arg) { + environment_->service_repository().add(std::move(arg)); +} + +void mock_server::add_endpoint(std::shared_ptr arg) { + environment_->endpoint_repository().add(std::move(arg)); +} + +bool mock_server::setup() { + if(setup_done_) return true; + bool success = true; + environment_->resource_repository().each([this, &success](resource& arg){ + if (! success) return; + success = arg.setup(*environment_); + }); + environment_->service_repository().each([this, &success](service& arg){ + if (! success) return; + success = arg.setup(*environment_); + }); + environment_->endpoint_repository().each([this, &success](endpoint& arg){ + if (! success) return; + success = arg.setup(*environment_); + }); + if(! success) { + LOG(ERROR) << "Server application framework setup phase failed."; + // shutdown already setup components + shutdown(); + } + setup_done_ = success; + return success; +} + +bool mock_server::start() { + if(! setup_done_) { + if(! setup()) { + // error logged in setup() already + return false; + } + } + bool success = true; + environment_->resource_repository().each([this, &success](resource& arg){ + if (! success) return; + success = arg.start(*environment_); + }); + environment_->service_repository().each([this, &success](service& arg){ + if (! success) return; + success = arg.start(*environment_); + }); + environment_->endpoint_repository().each([this, &success](endpoint& arg){ + if (! success) return; + success = arg.start(*environment_); + }); + if(! success) { + LOG(ERROR) << "Server application framework start phase failed."; + // shutdown already started components + shutdown(); + } + return success; +} + +bool mock_server::shutdown() { + // even if some components fails, continue all shutdown for clean-up + bool success = true; + environment_->endpoint_repository().each([this, &success](endpoint& arg){ + success = arg.shutdown(*environment_) && success; + }, true); + environment_->service_repository().each([this, &success](service& arg){ + success = arg.shutdown(*environment_) && success; + }, true); + environment_->resource_repository().each([this, &success](resource& arg){ + success = arg.shutdown(*environment_) && success; + }, true); + return success; +} + +mock_server::mock_server(framework::boot_mode mode, std::shared_ptr cfg) : + environment_(std::make_shared(mode, std::move(cfg))) { +} + +void add_core_components(mock_server& svr) { + svr.add_resource(std::make_shared()); + svr.add_resource(std::make_shared()); + + svr.add_service(std::make_shared()); + svr.add_service(std::make_shared()); + + svr.add_service(std::make_shared<::tateyama::service::mock_service>()); + svr.add_endpoint(std::make_shared()); +} + +} // namespace tateyama::server + + +int +main() { + return tateyama::server::mock_server_main(); +} diff --git a/mock/tateyama/server/mock_server.h b/mock/tateyama/server/mock_server.h new file mode 100644 index 00000000..84593154 --- /dev/null +++ b/mock/tateyama/server/mock_server.h @@ -0,0 +1,156 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace tateyama::server { + +using tateyama::framework::environment; +using tateyama::framework::component; +using tateyama::framework::resource; +using tateyama::framework::service; +using tateyama::framework::endpoint; + +/** + * @brief interface for tateyama components life cycle management + */ +class mock_server { +public: + /** + * @brief create new object + */ + mock_server() = default; + + /** + * @brief destruct object + */ + ~mock_server() = default; + + mock_server(mock_server const& other) = delete; + mock_server& operator=(mock_server const& other) = delete; + mock_server(mock_server&& other) noexcept = delete; + mock_server& operator=(mock_server&& other) noexcept = delete; + + /** + * @brief create new object with environment + * @param mode framework boot mode on this environment + */ + mock_server(framework::boot_mode mode, std::shared_ptr cfg); + + /** + * @brief add new resource to manage life cycle + * @details the priority between resource objects are determined by the order of addition. + * The objects added earlier have higher priority. + * @param arg tateyama resource + */ + void add_resource(std::shared_ptr arg); + + /** + * @brief add new service to manage life cycle + * @details the priority between service objects are determined by the order of addition. + * The objects added earlier have higher priority. + * @param arg tateyama service + */ + void add_service(std::shared_ptr arg); + + /** + * @brief add new endpoint to manage life cycle + * @details the priority between endpoint objects are determined by the order of addition. + * The objects added earlier have higher priority. + * @param arg tateyama endpoint + */ + void add_endpoint(std::shared_ptr arg); + + /** + * @brief find the resource by resource id + * @return the found resource + * @return nullptr if not found + */ + std::shared_ptr find_resource_by_id(component::id_type id); + + /** + * @brief find the resource for the given type + * @return the found resource + * @return nullptr if not found + */ + template std::shared_ptr find_resource() { + return environment_->resource_repository().find(); + } + + /** + * @brief find the service by service id + * @return the found service + * @return nullptr if not found + */ + std::shared_ptr find_service_by_id(component::id_type id); + + /** + * @brief find the service for the given type + * @return the found service + * @return nullptr if not found + */ + template std::shared_ptr find_service() { + return environment_->service_repository().find(); + } + + /** + * @brief setup the mock_server + * @details setup all resource, service and endpoint in this order appropriately. + * Components on the same category are setup in the priority order. Highest priority one is setup first. + */ + bool setup(); + + /** + * @brief start the mock_server + * @details setup (if not yet done) and start all resource, service and endpoint in this order appropriately. + * Components on the same category are started in the priority order. Highest priority one is started first. + */ + bool start(); + + /** + * @brief shutdown the mock_server + * @details shutdown all endpoint, service and resource in this order appropriately. + * Components on the same category are shutdown in the reverse priority order. Highest priority one is shutdown last. + */ + bool shutdown(); + +private: + std::shared_ptr environment_{std::make_shared()}; + std::shared_ptr cfg_{}; + bool setup_done_{false}; +}; + +/** + * @brief add tateyama core components + * @param svr the mock_server to add comopnents to + * @details This function add core components such as router, endpoint, + * that are built-in part of tateyama infrastructure. + */ +void add_core_components(mock_server& svr); + +} diff --git a/mock/tateyama/service/mock_request_handlers.cpp b/mock/tateyama/service/mock_request_handlers.cpp new file mode 100644 index 00000000..260907f9 --- /dev/null +++ b/mock/tateyama/service/mock_request_handlers.cpp @@ -0,0 +1,313 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include +#include +#include + +#include "mock_service.h" +#include "test_pattern.h" + +namespace tateyama::service { + +test_pattern pattern{}; + +// utilities +static void reply_ok(response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_result_only(); + (void) rmsg->mutable_success(); + + res->body(response_message.SerializeAsString()); +} + +static void dump_lob(request* req, const std::string& name, const std::string& local_path) { + std::cout << "req->has_blob(\"" << name << "\") = " << req->has_blob(name) << std::endl; + if (!local_path.empty()) { + std::cout << "local_path =" << local_path << std::endl; + } + try { + auto& blob_info = req->get_blob(name); + std::cout << "blob_info for " << name + << " : channel_name = " << blob_info.channel_name() + << " : path = " << blob_info.path().string() + << " : temporary = " << blob_info.is_temporary() + << std::endl; + } catch (std::runtime_error &ex) { + std::cout << "can't find blob_info for BlobChannelUp" << std::endl; + } +} + +using tateyama::api::server::data_channel; +using tateyama::api::server::writer; + +// request handlers +void mock_service::begin(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_begin(); + auto* success = rmsg->mutable_success(); + auto* transaction_handle = success->mutable_transaction_handle(); + transaction_handle->set_handle(12345); + + res->body(response_message.SerializeAsString()); +} + +void mock_service::prepare(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_prepare(); + auto* handle = rmsg->mutable_prepared_statement_handle(); + handle->set_handle(6789); + + res->body(response_message.SerializeAsString()); +} + +void mock_service::execute_prepared_statement(request* req, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + jogasaki::proto::sql::request::Request request_message{}; + request_message.ParseFromString(std::string(req->payload())); + auto& parameters = request_message.execute_prepared_statement().parameters(); + for (auto&& e: parameters) { + auto value_case = e.value_case(); + if (value_case == jogasaki::proto::sql::request::Parameter::ValueCase::kBlob) { + auto value = e.blob(); + dump_lob(req, value.channel_name(), value.data_case() == jogasaki::proto::sql::common::Blob::DataCase::kLocalPath ? value.local_path() : ""); + } + if (value_case == jogasaki::proto::sql::request::Parameter::ValueCase::kClob) { + auto value = e.clob(); + dump_lob(req, value.channel_name(), value.data_case() == jogasaki::proto::sql::common::Clob::DataCase::kLocalPath ? value.local_path() : ""); + } + } + + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_execute_result(); + auto* success = rmsg->mutable_success(); + auto* counter = success->add_counters(); + counter->set_type(jogasaki::proto::sql::response::ExecuteResult::INSERTED_ROWS); + counter->set_value(123); + + res->body(response_message.SerializeAsString()); +} + +void mock_service::execute_prepared_query(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + + std::shared_ptr ch; + (void) res->acquire_channel("ResultSetName", ch, 1); + std::shared_ptr wrt; + (void) ch->acquire(wrt); + + auto vw = std::make_shared>(*wrt); + + (void) vw->write_row_begin(pattern.size()); + pattern.foreach_blob([vw](std::size_t lob_id, std::string&, std::string&, std::string&){ + (void) vw->write_blob(1, lob_id); + }); + pattern.foreach_clob([vw](std::size_t lob_id, std::string&, std::string&, std::string&){ + (void) vw->write_clob(1, lob_id); + }); + (void) wrt->commit(); + (void) vw->write_end_of_contents(); + + (void) ch->release(*wrt); + (void) res->release_channel(*ch); + + jogasaki::proto::sql::response::Response head{}; + auto *eq = head.mutable_execute_query(); + eq->set_name("ResultSetName"); + auto *meta = eq->mutable_record_meta(); + pattern.foreach_blob([meta](std::size_t, std::string& name, std::string&, std::string&){ + auto *column = meta->add_columns(); + column->set_name(name); + column->set_atom_type(jogasaki::proto::sql::common::AtomType::BLOB); + }); + pattern.foreach_clob([meta](std::size_t, std::string& name, std::string&, std::string&){ + auto *column = meta->add_columns(); + column->set_name(name); + column->set_atom_type(jogasaki::proto::sql::common::AtomType::CLOB); + }); + res->body_head(head.SerializeAsString()); + + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_result_only(); + (void) rmsg->mutable_success(); + pattern.foreach_blob([res](std::size_t, std::string&, std::string& blob_channel, std::string& file_name){ + res->add_blob(std::make_unique(blob_channel, std::filesystem::path(file_name), false)); + }); + pattern.foreach_clob([res](std::size_t, std::string&, std::string& clob_channel, std::string& file_name){ + res->add_blob(std::make_unique(clob_channel, std::filesystem::path(file_name), false)); + }); + res->body(response_message.SerializeAsString()); +} + +void mock_service::get_large_object_data(request* req, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + + jogasaki::proto::sql::request::Request request_message{}; + request_message.ParseFromString(std::string(req->payload())); + auto& ref = request_message.get_large_object_data().reference(); + std::cout << "reference = " << ref.provider() << ":" << ref.object_id() << std::endl; + + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_get_large_object_data(); + if (auto name_opt = pattern.find(ref.object_id()); name_opt) { + auto* success = rmsg->mutable_success(); + success->set_channel_name(name_opt.value()); + } else { + auto* error = rmsg->mutable_error(); + error->set_detail("invalid object id"); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + } + res->body(response_message.SerializeAsString()); +} + +void mock_service::commit(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + reply_ok(res); +} +void mock_service::rollback(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + reply_ok(res); +} +void mock_service::dispose_prepared_statement(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + reply_ok(res); +} +void mock_service::dispose_transaction(request*, response* res) { + std::cout << "======== " << __func__ << " ========" << std::endl; + reply_ok(res); +} + +// does not support with the mock server +std::string does_not_support(const std::string& name) { + using namespace std::literals::string_literals; + return name + " is not supported with the mock server"s; +} + +void mock_service::execute_statement(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_prepare(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("execute_statement")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::execute_query(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_execute_result(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("execute_query")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::explain(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_explain(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("explain")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::execute_dump(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_result_only(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("execute_dump")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::execute_load(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_execute_result(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("execute_load")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::describe_table(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_describe_table(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::batch(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_execute_result(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("batch")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::list_tables(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_list_tables(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("list_tables")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::get_search_path(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_get_search_path(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("get_search_path")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::get_error_info(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_get_error_info(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("get_error_info")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::explain_by_text(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_explain(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("explain_by_text")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} +void mock_service::extract_statement_info(request*, response* res) { + jogasaki::proto::sql::response::Response response_message{}; + auto *rmsg = response_message.mutable_extract_statement_info(); + auto* error = rmsg->mutable_error(); + error->set_detail(does_not_support("extract_statement_info")); + error->set_code(::jogasaki::proto::sql::error::Code::SQL_SERVICE_EXCEPTION); + + res->body(response_message.SerializeAsString()); +} + +} // namespace tateyama::service diff --git a/mock/tateyama/service/mock_service.cpp b/mock/tateyama/service/mock_service.cpp new file mode 100644 index 00000000..029f25c2 --- /dev/null +++ b/mock/tateyama/service/mock_service.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "mock_service.h" + +using takatori::util::throw_exception; + +namespace tateyama::service { + +namespace framework = tateyama::framework; + +constexpr static std::string_view log_location_prefix = "/:tateyama:mock_service:logging "; + +framework::component::id_type mock_service::id() const noexcept { + return tag; +} + +bool mock_service::setup(framework::environment&) { + return true; +} + +bool mock_service::start(framework::environment&) { + return true; +} + +bool mock_service::shutdown(framework::environment&) { + return true; +} + +mock_service::~mock_service() { + VLOG(log_info) << "/:tateyama:lifecycle:component: " << component_label; +} + +bool mock_service::operator()(std::shared_ptr req, [[maybe_unused]] std::shared_ptr res) { + jogasaki::proto::sql::request::Request proto_req { }; + res->session_id(req->session_id()); + auto s = req->payload(); + if (!proto_req.ParseFromArray(s.data(), static_cast(s.size()))) { + throw_exception(std::logic_error{"proto_req.ParseFromArray failed"}); + } + switch (proto_req.request_case()) { + case jogasaki::proto::sql::request::Request::kBegin: + begin(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kPrepare: + prepare(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecuteStatement: + execute_statement(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecuteQuery: + execute_query(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecutePreparedStatement: + execute_prepared_statement(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecutePreparedQuery: + execute_prepared_query(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kCommit: + commit(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kRollback: + rollback(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kDisposePreparedStatement: + dispose_prepared_statement(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExplain: + explain(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecuteDump: + execute_dump(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExecuteLoad: + execute_load(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kDescribeTable: + describe_table(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kBatch: + batch(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kListTables: + list_tables(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kGetSearchPath: + get_search_path(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kGetErrorInfo: + get_error_info(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kDisposeTransaction: + dispose_transaction(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExplainByText: + explain_by_text(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kExtractStatementInfo: + extract_statement_info(req.get(), res.get()); + break; + case jogasaki::proto::sql::request::Request::kGetLargeObjectData: + get_large_object_data(req.get(), res.get()); + break; + default: + throw_exception(std::logic_error{"illegal command_case"}); + } + return true; +} + +std::string_view mock_service::label() const noexcept { + return component_label; +} + +} // namespace tateyama::service diff --git a/mock/tateyama/service/mock_service.h b/mock/tateyama/service/mock_service.h new file mode 100644 index 00000000..3229f564 --- /dev/null +++ b/mock/tateyama/service/mock_service.h @@ -0,0 +1,129 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace tateyama::service { + +using tateyama::api::server::request; +using tateyama::api::server::response; + +/** +* @brief mock_service for the mock_server +* @details This object provides mock service that mimics sql service +* This object should be responsible only for life-cycle management. +*/ +class mock_service : public framework::service { +public: + static constexpr id_type tag = framework::service_id_sql; + + [[nodiscard]] id_type id() const noexcept override; + + //@brief human readable label of this component + static constexpr std::string_view component_label = "mock_service"; + + /** + * @brief setup the component (the state will be `ready`) + */ + bool setup(framework::environment&) override; + + /** + * @brief start the component (the state will be `activated`) + */ + bool start(framework::environment&) override; + + /** + * @brief shutdown the component (the state will be `deactivated`) + */ + bool shutdown(framework::environment&) override; + + bool operator()( + std::shared_ptr req, + std::shared_ptr res) override; + + /** + * @brief create empty object + */ + mock_service() = default; + + mock_service(mock_service const& other) = delete; + mock_service& operator=(mock_service const& other) = delete; + mock_service(mock_service&& other) noexcept = delete; + mock_service& operator=(mock_service&& other) noexcept = delete; + + /** + * @brief destructor the object + */ + ~mock_service() override; + + /** + * @see `tateyama::framework::component::label()` + */ + [[nodiscard]] std::string_view label() const noexcept override; + + void begin(request* req, response* res); + void prepare(request* req, response* res); + void execute_statement(request* req, response* res); + void execute_query(request* req, response* res); + void execute_prepared_statement(request* req, response* res); + void execute_prepared_query(request* req, response* res); + void commit(request* req, response* res); + void rollback(request* req, response* res); + void dispose_prepared_statement(request* req, response* res); + void explain(request* req, response* res); + void execute_dump(request* req, response* res); + void execute_load(request* req, response* res); + void describe_table(request* req, response* res); + void batch(request* req, response* res); + void list_tables(request* req, response* res); + void get_search_path(request* req, response* res); + void get_error_info(request* req, response* res); + void dispose_transaction(request* req, response* res); + void explain_by_text(request* req, response* res); + void extract_statement_info(request* req, response* res); + void get_large_object_data(request* req, response* res); +}; + +class blob_info_for_test : public tateyama::api::server::blob_info { +public: + blob_info_for_test(std::string_view channel_name, std::filesystem::path path, bool temporary) + : channel_name_(channel_name), path_(std::move(path)), temporary_(temporary) { + } + [[nodiscard]] std::string_view channel_name() const noexcept override { + return channel_name_; + } + [[nodiscard]] std::filesystem::path path() const noexcept override { + return path_; + } + [[nodiscard]] bool is_temporary() const noexcept override { + return temporary_; + } + void dispose() override { + } +private: + const std::string channel_name_{}; + const std::filesystem::path path_{}; + const bool temporary_{}; +}; + +} // namespace tateyama::mock diff --git a/mock/tateyama/service/test_pattern.h b/mock/tateyama/service/test_pattern.h new file mode 100644 index 00000000..76d104be --- /dev/null +++ b/mock/tateyama/service/test_pattern.h @@ -0,0 +1,112 @@ +/* + * Copyright 2018-2025 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tateyama::service { + +class test_file { +public: + test_file(const std::string& name) : name_(name) { + } + void clear() { + std::filesystem::remove(name_); + } + void write(std::string_view content) { + std::ofstream file(name_); + file << content << std::endl; + file.close(); + } +private: std::string name_{}; +}; + +class test_pattern { +public: + test_pattern() { + using namespace std::literals::string_literals; + blobs_.emplace(13578, std::make_tuple("blob1"s, "BlobChannelDown1"s, "/tmp/BlobFileDown1"s)); + blobs_.emplace(24680, std::make_tuple("blob2"s, "BlobChannelDown2"s, "/tmp/BlobFileDown2"s)); + clobs_.emplace(98765, std::make_tuple("clob"s, "ClobChannelDown"s, "/tmp/ClobFileDown"s)); + + initialize_file(); + } + ~test_pattern() { + clear_file(); + } + std::size_t size() { + return blobs_.size() + clobs_.size(); + } + void foreach_blob(std::function const& f) { + for(auto&& e: blobs_) { + auto&& t = e.second; + f(e.first, std::get<0>(t), std::get<1>(t), std::get<2>(t)); + } + } + void foreach_clob(std::function const& f) { + for(auto&& e: clobs_) { + auto&& t = e.second; + f(e.first, std::get<0>(t), std::get<1>(t), std::get<2>(t)); + } + } + std::optional find(std::size_t id) { + if (auto it = blobs_.find(id); it != blobs_.end()) { + return std::get<1>(it->second); + } + if (auto it = clobs_.find(id); it != clobs_.end()) { + return std::get<1>(it->second); + } + return std::nullopt; + } + +private: + std::map> blobs_{}; + std::map> clobs_{}; + + void initialize_file() { + using namespace std::literals::string_literals; + foreach_blob([](std::size_t, std::string& column, std::string& channel, std::string& file_name){ + test_file file{file_name}; + file.clear(); + file.write("this is a test file of blob, column = "s + column + ", channel = "s + channel); + }); + foreach_clob([](std::size_t, std::string& column, std::string& channel, std::string& file_name){ + test_file file{file_name}; + file.clear(); + file.write("this is a test file of clob, column = "s + column + ", channel = "s + channel); + }); + } + void clear_file() { + foreach_blob([](std::size_t, std::string&, std::string&, std::string& file_name){ + test_file file{file_name}; + file.clear(); + }); + foreach_clob([](std::size_t, std::string&, std::string&, std::string& file_name){ + test_file file{file_name}; + file.clear(); + }); + } + + }; + +} // namespace tateyama::mock diff --git a/src/tateyama/endpoint/ipc/bootstrap/ipc_endpoint.h b/src/tateyama/endpoint/ipc/bootstrap/ipc_endpoint.h index e00ee22c..c88ac6a9 100644 --- a/src/tateyama/endpoint/ipc/bootstrap/ipc_endpoint.h +++ b/src/tateyama/endpoint/ipc/bootstrap/ipc_endpoint.h @@ -74,12 +74,14 @@ class ipc_endpoint : public endpoint { bool start(environment& env) override { listener_thread_ = std::thread(std::ref(*listener_)); listener_->arrive_and_wait(); - auto request_service = env.service_repository().find(); - request_service->register_endpoint_listener(listener_); - auto diagnostic_resource = env.resource_repository().find(); - diagnostic_resource->add_print_callback("tateyama_ipc_endpoint", [this](std::ostream& os) { - listener_->print_diagnostic(os); - }); + if (auto request_service = env.service_repository().find(); request_service) { + request_service->register_endpoint_listener(listener_); + } + if (auto diagnostic_resource = env.resource_repository().find(); diagnostic_resource) { + diagnostic_resource->add_print_callback("tateyama_ipc_endpoint", [this](std::ostream& os) { + listener_->print_diagnostic(os); + }); + } return true; } @@ -95,8 +97,9 @@ class ipc_endpoint : public endpoint { listener_thread_.join(); } listener_.reset(); - auto diagnostic_resource = env.resource_repository().find(); - diagnostic_resource->remove_print_callback("tateyama_ipc_endpoint"); + if (auto diagnostic_resource = env.resource_repository().find(); diagnostic_resource) { + diagnostic_resource->remove_print_callback("tateyama_ipc_endpoint"); + } return true; } diff --git a/src/tateyama/endpoint/ipc/bootstrap/ipc_listener.h b/src/tateyama/endpoint/ipc/bootstrap/ipc_listener.h index 6c522aaf..cd3afd29 100644 --- a/src/tateyama/endpoint/ipc/bootstrap/ipc_listener.h +++ b/src/tateyama/endpoint/ipc/bootstrap/ipc_listener.h @@ -140,7 +140,7 @@ class ipc_listener : public tateyama::endpoint::common::listener_common { auto enable_timeout = enable_timeout_opt.value(); LOG(INFO) << tateyama::endpoint::common::session_config_prefix << "enable_timeout: " << enable_timeout << ", " - << "timeout is enabled."; + << "whether timeout is enabled or not."; if (enable_timeout) { auto refresh_timeout_opt = session_config->get("refresh_timeout"); diff --git a/src/tateyama/endpoint/stream/bootstrap/stream_endpoint.h b/src/tateyama/endpoint/stream/bootstrap/stream_endpoint.h index 727401e1..d5a076a3 100644 --- a/src/tateyama/endpoint/stream/bootstrap/stream_endpoint.h +++ b/src/tateyama/endpoint/stream/bootstrap/stream_endpoint.h @@ -79,12 +79,14 @@ class stream_endpoint : public endpoint { if (enabled_) { listener_thread_ = std::thread(std::ref(*listener_)); listener_->arrive_and_wait(); - auto request_service = env.service_repository().find(); - request_service->register_endpoint_listener(listener_); - auto diagnostic_resource = env.resource_repository().find(); - diagnostic_resource->add_print_callback("tateyama_stream_endpoint", [this](std::ostream& os) { - listener_->print_diagnostic(os); - }); + if (auto request_service = env.service_repository().find(); request_service) { + request_service->register_endpoint_listener(listener_); + } + if (auto diagnostic_resource = env.resource_repository().find(); diagnostic_resource) { + diagnostic_resource->add_print_callback("tateyama_stream_endpoint", [this](std::ostream& os) { + listener_->print_diagnostic(os); + }); + } } return true; } @@ -92,7 +94,7 @@ class stream_endpoint : public endpoint { /** * @brief shutdown the component (the state will be `deactivated`) */ - bool shutdown(environment&) override { + bool shutdown(environment& env) override { if (enabled_) { // For clean up, shutdown can be called multiple times with/without setup()/start(). if(listener_thread_.joinable()) { @@ -102,6 +104,9 @@ class stream_endpoint : public endpoint { listener_thread_.join(); } listener_.reset(); + if (auto diagnostic_resource = env.resource_repository().find(); diagnostic_resource) { + diagnostic_resource->remove_print_callback("tateyama_stream_endpoint"); + } } return true; } diff --git a/src/tateyama/endpoint/stream/bootstrap/stream_listener.h b/src/tateyama/endpoint/stream/bootstrap/stream_listener.h index aef5a1a1..95250a73 100644 --- a/src/tateyama/endpoint/stream/bootstrap/stream_listener.h +++ b/src/tateyama/endpoint/stream/bootstrap/stream_listener.h @@ -107,7 +107,7 @@ class stream_listener : public tateyama::endpoint::common::listener_common { auto enable_timeout = enable_timeout_opt.value(); LOG(INFO) << tateyama::endpoint::common::session_config_prefix << "enable_timeout: " << enable_timeout << ", " - << "timeout is enabled."; + << "whether timeout is enabled or not."; if (enable_timeout) { auto refresh_timeout_opt = session_config->get("refresh_timeout");